pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
\if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
232+ messages / 12 participants
[nested] [flat]

* \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-22 23:56  Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-22 23:56 UTC (permalink / raw)
  To: Michael Paquier <michael.paquier@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Pavel Stehule <pavel.stehule@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Andrew Dunstan <andrew@dunslane.net>; pgsql-hackers

>
>
> Fabien is pressed for time, so I've been speaking with him out-of-thread
> about how I should go about implementing it.
>
> The v1 patch will be \if <expr>, \elseif <expr>, \else, \endif, where
> <expr> will be naively evaluated via ParseVariableBool().
>
> \ifs and \endifs must be in the same "file" (each MainLoop will start a
> new if-stack). This is partly for sanity (you can see the pairings unless
> the programmer is off in \gset meta-land), partly for ease  of design (data
> structures live in MainLoop), but mostly because it would an absolute
> requirement if we ever got around to doing \while.
>
> I hope to have something ready for the next commitfest.
>
> As for the fate of \quit_if, I can see it both ways. On the one hand,
> it's super-simple, already written, and handy.
>
> On the other hand, it's easily replaced by
>
> \if <expr>
>     \q
> \endif
>
>
> So I'll leave that as a separate reviewable patch.
>
> As for loops, I don't think anyone was pushing for implementing \while
> now, only to have a decision about what it would look like and how it would
> work. There's a whole lot of recording infrastructure (the input could be a
> stream) needed to make it happen. Moreover, I think \gexec scratched a
> lot of the itches that would have been solved via a psql looping structure.
>


And here's the patch. I've changed the subject line and will be submitting
a new entry to the commitfest.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 9915731..2c3fccd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2007,6 +2007,83 @@ hello 10
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\if</literal> <replaceable class="parameter">expr</replaceable></term>
+        <term><literal>\elseif</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>
+\if true
+    \if 1
+        \if yes
+            \if on
+                \echo 'all true'
+all true
+            \endif
+        \endif
+    \endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+    \echo 'all false'
+all false
+\endif
+\if true
+\else
+    \echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+    \echo 'should not print #2'
+\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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </para>
+        <para>
+        The <command>\if</command> and <command>\elseif</command> commands
+        read the rest of the line and evaluate it as a boolean expression.
+        Currently, expressions are limited to the same values allowed for
+        other option booleans (true, false, 1, 0, on, off, yes, no, etc).
+        </para>
+        <para>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elseif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elseif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 4139b77..d4e0bb8 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) && psqlscan_branch_active(scan_state))
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,32 @@ read_connect_arg(PsqlScanState scan_state)
 	return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, const char *action)
+{
+	bool	result = false;
+	char	*expr = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	if (expr)
+	{
+		if (ParseVariableBool(expr, action))
+			result = true;
+		free(expr);
+	}
+	return result;
+}
+
+static bool
+is_branching_command(const char *cmd)
+{
+	return ((strcmp(cmd, "if") == 0 || \
+			strcmp(cmd, "elseif") == 0 || \
+			strcmp(cmd, "else") == 0 || \
+			strcmp(cmd, "endif") == 0));
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +234,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!psqlscan_branch_active(scan_state) && !is_branching_command(cmd) )
+	{
+		/* Continue with an empty buffer as if the command were never read */
+		resetPQExpBuffer(query_buf); /* TODO: is this needed? */
+		psql_scan_reset(scan_state); /* TODO: is this needed or just for interactive? */
+		return status;
+	}
+
 	/*
 	 * \a -- toggle field alignment This makes little sense but we keep it
 	 * around.
@@ -984,6 +1019,84 @@ exec_command(const char *cmd,
 		}
 	}
 
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		if (psqlscan_branch_active(scan_state))
+		{
+			if (read_boolean_expression(scan_state, "\\if"))
+				new_if_state = IFSTATE_TRUE;
+			else
+				new_if_state = IFSTATE_FALSE;
+		}
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elseif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\elseif\n");
+
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/* inactive branch, do nothing */
+				break;
+			case IFSTATE_TRUE:
+				/* just finished true section of active branch */
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				break;
+			case IFSTATE_FALSE:
+				/* determine if this section is true or not */
+				if (read_boolean_expression(scan_state, "\\elseif"))
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elseif after \\else\n");
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\else\n");
+
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+				/* just finished true section of active branch */
+			case IFSTATE_IGNORED:
+				/* whole branch was inactive */
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished true section of active branch */
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\endif\n");
+		psqlscan_branch_end_state(scan_state);
+		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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..8f12e55 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (psqlscan_branch_active(scan_state))
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -352,21 +361,28 @@ MainLoop(FILE *source)
 				if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
 					query_buf->len == 0)
 				{
+					/* TODO check if-then-skip-state */
 					/* copy previous buffer to current for handling */
 					appendPQExpBufferStr(query_buf, previous_buf->data);
 				}
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (psqlscan_branch_active(scan_state))
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +441,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (psqlscan_branch_active(scan_state))
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +472,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..881c4f8 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+	state->branch_block_active = true;
+
 	return state;
 }
 
@@ -919,6 +922,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 +1436,103 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_empty(PsqlScanState state)
+{
+	return (state->branch_stack == NULL);
+}
+
+/*
+ * 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)
+{
+	return state->branch_block_active;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_update_active
+ *
+ * Scan the branch_stack to determine whether the next statements
+ * can execute or should be skipped. Cache this result in
+ * branch_block_active.
+ */
+static void
+psqlscan_branch_update_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	state->branch_block_active  = ( (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;
+	psqlscan_branch_update_active(state);
+	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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	psqlscan_branch_update_active(state);
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ * 
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	psqlscan_branch_update_active(state);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..e9773ca 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 \elseif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elseif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elseif 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,12 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
+	bool branch_block_active;
 } PsqlScanStateData;
 
 
@@ -141,4 +171,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..7492a92 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,33 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\endif
+		\endif
+	\endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+	\echo 'all false'
+all false
+\endif
+\if true
+\else
+	\echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+	\echo 'should not print #2'
+\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 900aa7e..ef1be30 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,36 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\endif
+		\endif
+	\endif
+\endif
+
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+	\echo 'all false'
+\endif
+
+\if true
+\else
+	\echo 'should not print #1'
+\endif
+
+\if false
+\elseif true
+\else
+	\echo 'should not print #2'
+\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

Attachments:

  [text/plain] 0001.if_endif.diff (17.8K, ../../CADkLM=c94OSRTnat=LX0ivNq4pxDNeoomFfYvBKM5N_xfmLtAA@mail.gmail.com/3-0001.if_endif.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 9915731..2c3fccd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2007,6 +2007,83 @@ hello 10
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\if</literal> <replaceable class="parameter">expr</replaceable></term>
+        <term><literal>\elseif</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>
+\if true
+    \if 1
+        \if yes
+            \if on
+                \echo 'all true'
+all true
+            \endif
+        \endif
+    \endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+    \echo 'all false'
+all false
+\endif
+\if true
+\else
+    \echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+    \echo 'should not print #2'
+\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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </para>
+        <para>
+        The <command>\if</command> and <command>\elseif</command> commands
+        read the rest of the line and evaluate it as a boolean expression.
+        Currently, expressions are limited to the same values allowed for
+        other option booleans (true, false, 1, 0, on, off, yes, no, etc).
+        </para>
+        <para>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elseif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elseif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 4139b77..d4e0bb8 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) && psqlscan_branch_active(scan_state))
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,32 @@ read_connect_arg(PsqlScanState scan_state)
 	return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, const char *action)
+{
+	bool	result = false;
+	char	*expr = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	if (expr)
+	{
+		if (ParseVariableBool(expr, action))
+			result = true;
+		free(expr);
+	}
+	return result;
+}
+
+static bool
+is_branching_command(const char *cmd)
+{
+	return ((strcmp(cmd, "if") == 0 || \
+			strcmp(cmd, "elseif") == 0 || \
+			strcmp(cmd, "else") == 0 || \
+			strcmp(cmd, "endif") == 0));
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +234,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!psqlscan_branch_active(scan_state) && !is_branching_command(cmd) )
+	{
+		/* Continue with an empty buffer as if the command were never read */
+		resetPQExpBuffer(query_buf); /* TODO: is this needed? */
+		psql_scan_reset(scan_state); /* TODO: is this needed or just for interactive? */
+		return status;
+	}
+
 	/*
 	 * \a -- toggle field alignment This makes little sense but we keep it
 	 * around.
@@ -984,6 +1019,84 @@ exec_command(const char *cmd,
 		}
 	}
 
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		if (psqlscan_branch_active(scan_state))
+		{
+			if (read_boolean_expression(scan_state, "\\if"))
+				new_if_state = IFSTATE_TRUE;
+			else
+				new_if_state = IFSTATE_FALSE;
+		}
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elseif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\elseif\n");
+
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/* inactive branch, do nothing */
+				break;
+			case IFSTATE_TRUE:
+				/* just finished true section of active branch */
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				break;
+			case IFSTATE_FALSE:
+				/* determine if this section is true or not */
+				if (read_boolean_expression(scan_state, "\\elseif"))
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elseif after \\else\n");
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\else\n");
+
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+				/* just finished true section of active branch */
+			case IFSTATE_IGNORED:
+				/* whole branch was inactive */
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished true section of active branch */
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+			psql_error("encountered un-matched \\endif\n");
+		psqlscan_branch_end_state(scan_state);
+		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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..8f12e55 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (psqlscan_branch_active(scan_state))
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -352,21 +361,28 @@ MainLoop(FILE *source)
 				if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
 					query_buf->len == 0)
 				{
+					/* TODO check if-then-skip-state */
 					/* copy previous buffer to current for handling */
 					appendPQExpBufferStr(query_buf, previous_buf->data);
 				}
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (psqlscan_branch_active(scan_state))
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +441,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (psqlscan_branch_active(scan_state))
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +472,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..881c4f8 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+	state->branch_block_active = true;
+
 	return state;
 }
 
@@ -919,6 +922,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 +1436,103 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_empty(PsqlScanState state)
+{
+	return (state->branch_stack == NULL);
+}
+
+/*
+ * 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)
+{
+	return state->branch_block_active;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_update_active
+ *
+ * Scan the branch_stack to determine whether the next statements
+ * can execute or should be skipped. Cache this result in
+ * branch_block_active.
+ */
+static void
+psqlscan_branch_update_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	state->branch_block_active  = ( (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;
+	psqlscan_branch_update_active(state);
+	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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	psqlscan_branch_update_active(state);
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ * 
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	psqlscan_branch_update_active(state);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..e9773ca 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 \elseif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elseif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elseif 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,12 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
+	bool branch_block_active;
 } PsqlScanStateData;
 
 
@@ -141,4 +171,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..7492a92 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,33 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\endif
+		\endif
+	\endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+	\echo 'all false'
+all false
+\endif
+\if true
+\else
+	\echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+	\echo 'should not print #2'
+\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 900aa7e..ef1be30 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,36 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\endif
+		\endif
+	\endif
+\endif
+
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+	\echo 'all false'
+\endif
+
+\if true
+\else
+	\echo 'should not print #1'
+\endif
+
+\if false
+\elseif true
+\else
+	\echo 'should not print #2'
+\endif
+
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-23 20:37  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-01-23 20:37 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


> And here's the patch. I've changed the subject line and will be submitting
> a new entry to the commitfest.

A few comments:

Patch applies, make check is ok, but currently really too partial.

I do not like the choice of "elseif", which exists in PHP & VB. PL/pgSQL 
has "ELSIF" like perl, that I do not like much, though. Given the syntax 
and behavioral proximity with cpp, I suggest that "elif" would be a better 
choice.

Documentation: I do not think that the systematic test-like example in the 
documentation is relevant, a better example should be shown. The list of 
what is considered true or false should be told explicitely, not end with 
"etc" that is everyone to guess.

Tests: On principle they should include echos in all non executed branches 
to reassure that they are indeed not executed.

Also, no error cases are tested. They should be. Maybe it is not very easy 
to test some cases which expect to make psql generate errors, but I feel 
they are absolutely necessary for such a feature.

1: unrecognized value "whatever" for "\if"; assuming "on"

I do not think that the script should continue with such an assumption.

2: encountered \else after \else ... "# ERROR BEFORE"

Even with ON_ERROR_STOP activated the execution continues.

3: no \endif

Error reported, but it does not stop even with ON_ERROR_STOP.

4: include with bad nesting...

Again, even with ON_ERROR_STOP, the execution continues...


Code:

   -       if (status != PSQL_CMD_ERROR)
   +       if ((status != PSQL_CMD_ERROR) && psqlscan_branch_active(scan_state))

Why the added parenthesis?

   case ...: case ...:

The project rules are to add explicit /* PASSTHROUGH */ comment.

There are spaces at the end of one line in a comment about 
psqlscan_branch_end_state.

There are multiple "TODO" comments in the file... Is it done or work in 
progress?

Usually in pg comments about a function do not repeat the function name. 
For very short comment, they are /* inlined */ on one line. Use pg comment 
style.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-23 20:39  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-01-23 20:39 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


> A few comments:

Argh, better with the attachements:-(

-- 
Fabien.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/x-sql] if-error-1.sql (77B, ../../alpine.DEB.2.20.1701232138530.31421@lancre/2-if-error-1.sql)
  download

  [application/x-sql] if-error-2.sql (109B, ../../alpine.DEB.2.20.1701232138530.31421@lancre/3-if-error-2.sql)
  download

  [application/x-sql] if-error-3.sql (59B, ../../alpine.DEB.2.20.1701232138530.31421@lancre/4-if-error-3.sql)
  download

  [application/x-sql] if-error-4.sql (160B, ../../alpine.DEB.2.20.1701232138530.31421@lancre/5-if-error-4.sql)
  download

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-23 21:12  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 3 replies; 232+ messages in thread

From: Corey Huinker @ 2017-01-23 21:12 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: pgsql-hackers

>
> I do not like the choice of "elseif", which exists in PHP & VB. PL/pgSQL
> has "ELSIF" like perl, that I do not like much, though. Given the syntax
> and behavioral proximity with cpp, I suggest that "elif" would be a better
> choice.
>

I'm personally indifferent to elif vs elsif vs elseif, but I think elseif
was the consensus. It's easy enough to change.


>
> Documentation: I do not think that the systematic test-like example in the
> documentation is relevant, a better example should be shown. The list of
> what is considered true or false should be told explicitely, not end with
> "etc" that is everyone to guess.
>

It was copied from the regression test. I knew there would be follow-up
suggestions for what should be shown.


>
> Tests: On principle they should include echos in all non executed branches
> to reassure that they are indeed not executed.
>

Will do.


>
> Also, no error cases are tested. They should be. Maybe it is not very easy
> to test some cases which expect to make psql generate errors, but I feel
> they are absolutely necessary for such a feature.
>

Will do.


>
> 1: unrecognized value "whatever" for "\if"; assuming "on"
>
> I do not think that the script should continue with such an assumption.
>

I agree, and this means we can't use ParseVariableBool() as-is. I already
broke out argument reading to it's own function, knowing that it'd be the
stub for expressions. So I guess we start that now. What subset of true-ish
values do you think we should support? If we think that real expressions
are possible soon, we could only allow 'true' and 'false' for now, but if
we expect that expressions might not make it into v10, then perhaps we
should support the same text values that coerce to booleans on the server
side.


>
> 2: encountered \else after \else ... "# ERROR BEFORE"
>
> Even with ON_ERROR_STOP activated the execution continues.


> 3: no \endif
>
> Error reported, but it does not stop even with ON_ERROR_STOP.
>
> 4: include with bad nesting...
>
> Again, even with ON_ERROR_STOP, the execution continues...
>

All valid issues. Will add those to the regression as well (with
ON_ERROR_STOP disabled, obviously).


>
>
> Code:
>
>   -       if (status != PSQL_CMD_ERROR)
>   +       if ((status != PSQL_CMD_ERROR) && psqlscan_branch_active(scan_st
> ate))
>
> Why the added parenthesis?
>

Will fix.


>
>   case ...: case ...:
>
> The project rules are to add explicit /* PASSTHROUGH */ comment.
>

Will do.


>
> There are spaces at the end of one line in a comment about
> psqlscan_branch_end_state.
>
> There are multiple "TODO" comments in the file... Is it done or work in
> progress?
>

I forgot to remove them. But it would be wildly optimistic of me to think
there would be no more work for me after the first patch submission.


>
> Usually in pg comments about a function do not repeat the function name.
> For very short comment, they are /* inlined */ on one line. Use pg comment
> style.


In that case, I was copying the style found in other functions psqlscan.l -
I'm happy to remove it.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-23 23:03  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-23 23:03 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: pgsql-hackers

On Mon, Jan 23, 2017 at 4:12 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> I do not like the choice of "elseif", which exists in PHP & VB. PL/pgSQL
>> has "ELSIF" like perl, that I do not like much, though. Given the syntax
>> and behavioral proximity with cpp, I suggest that "elif" would be a better
>> choice.
>>
>
> I'm personally indifferent to elif vs elsif vs elseif, but I think elseif
> was the consensus. It's easy enough to change.
>

Went with elsif to follow pl/pgsql. In reviewing the original thread it
seemed that "elif" was linked to "fi" and that got some negative feedback.


>
>
>>
>> Documentation: I do not think that the systematic test-like example in
>> the documentation is relevant, a better example should be shown. The list
>> of what is considered true or false should be told explicitely, not end
>> with "etc" that is everyone to guess.
>>
>
> It was copied from the regression test. I knew there would be follow-up
> suggestions for what should be shown.
>

Work on this is pending discussion of what true/false values we should
allow, and if values outside of those is an error.


>>
>>   case ...: case ...:
>>
>> The project rules are to add explicit /* PASSTHROUGH */ comment.
>>
>
> Will do.
>

I went looking for other examples of explicit /* PASSTHROUGH */ comments
and could find none. Could you explain what it is you want me to fix with
the switch statements?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 06:06  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-24 06:06 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


Hello,

>> I'm personally indifferent to elif vs elsif vs elseif, but I think elseif
>> was the consensus. It's easy enough to change.
>
> Went with elsif to follow pl/pgsql. In reviewing the original thread it
> seemed that "elif" was linked to "fi" and that got some negative feedback.

As I understood it, the negative feeback was really about sh inspiration 
"if/fi", not about elif/elsif/elseif. I do not think that there was an
expressed consensus about the later.

Else-if variants from different languages include:

  VB: If ElseIf Else End If (with optional Then)
  PHP: if {} elseif {} else {}
  Tcl: if {} elseif {} else {}

  PL/pgSQL: IF ... THEN ... ELSIF ... ELSE ... END IF;
  Perl: if {} elsif {} else {}
  Ruby: if elsif else end

  CPP: #if #elif #else #endif
  Python: if : elif: else:
  bash: if [] then ... elif ... else ... fi

The closest case is CPP with its line-oriented #-prefix syntax, and I 
still think that we should do it like that.

> I went looking for other examples of explicit /* PASSTHROUGH */ comments
> and could find none. Could you explain what it is you want me to fix with
> the switch statements?

Sorry, I got it wrong. The comment (which is FALLTHROUGH or FALL THROUGH, 
not PASSTHROUGH) is required if there is specific code in a case and the 
case is expected to continue with executing the next case code as well. 
This is quite rare, and it is not the case for your code, which just has 
some comments in one case.

-- 
Fabien


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 06:15  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-24 06:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


>> 1: unrecognized value "whatever" for "\if"; assuming "on"
>>
>> I do not think that the script should continue with such an assumption.
>
> I agree, and this means we can't use ParseVariableBool() as-is. I 
> already broke out argument reading to it's own function, knowing that 
> it'd be the stub for expressions. So I guess we start that now. What 
> subset of true-ish values do you think we should support? If we think 
> that real expressions are possible soon, we could only allow 'true' and 
> 'false' for now, but if we expect that expressions might not make it 
> into v10, then perhaps we should support the same text values that 
> coerce to booleans on the server side.

Hmmm. I would text value that coerce to true? I would also accept non-zero 
integers (eg SELECT 1::BOOL; -- t).

I would suggest to assume false on everything else, and/or maybe to ignore 
the whole if/endif section in such cases.

> All valid issues. Will add those to the regression as well (with
> ON_ERROR_STOP disabled, obviously).

ISTM that with TAP test you can check for error returns, so maybe this can 
be done.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 06:22  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-01-24 06:22 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: pgsql-hackers

>
>
> As I understood it, the negative feeback was really about sh inspiration
> "if/fi", not about elif/elsif/elseif. I do not think that there was an
> expressed consensus about the later.
>

True


> The closest case is CPP with its line-oriented #-prefix syntax, and I
> still think that we should do it like that.


Given that the pl/pgsql syntax has a space between "end" and "if", I have
to agree. It's easy enough to change back if others can make a convincing
argument for something else.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 07:54  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-24 07:54 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: pgsql-hackers

On Tue, Jan 24, 2017 at 1:15 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> 1: unrecognized value "whatever" for "\if"; assuming "on"
>>>
>>> I do not think that the script should continue with such an assumption.
>>>
>>
>> I agree, and this means we can't use ParseVariableBool() as-is. I already
>> broke out argument reading to it's own function, knowing that it'd be the
>> stub for expressions. So I guess we start that now. What subset of true-ish
>> values do you think we should support? If we think that real expressions
>> are possible soon, we could only allow 'true' and 'false' for now, but if
>> we expect that expressions might not make it into v10, then perhaps we
>> should support the same text values that coerce to booleans on the server
>> side.
>>
>
> Hmmm. I would text value that coerce to true? I would also accept non-zero
> integers (eg SELECT 1::BOOL; -- t).
>


The docs (doc/src/sgml/datatype.sgml) list TRUE 't' 'true' 'y' 'yes' 'on'
'1'    vs FALSE 'f' 'false' 'n' 'no' 'off' '0'

However, src/test/regress/expected/boolean.out shows that there's some
flexiblity there with spaces, even before you inspect parse_bool_with_len()
in src/backend/utils/adt/bool.c.

If we could bring src/backend/utils/adt/bool.c across the server/client
wall, I would just use parse_bool_with_len as-is.

As it is, given that whatever I do is temporary until real expressions come
into place, that wouldn't be a terrible amount of code copying, and we'd
still have a proto-expression that probably isn't going to conflict with
whatever expression syntax we do chose. Having said that, if anyone can see
ANY reason that some subset of the existing bool-friendly strings would
cause a problem with the expression syntax we do hope to use, speak now and
we can restrict the accepted values.


> I would suggest to assume false on everything else, and/or maybe to ignore
> the whole if/endif section in such cases.


+1, it also halves the number of values we have to support later.


> ISTM that with TAP test you can check for error returns, so maybe this can
> be done.


I'll have to educate myself on TAP tests.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 08:58  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-24 08:58 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


>> I would suggest to assume false on everything else, and/or maybe to ignore
>> the whole if/endif section in such cases.
>
> +1, it also halves the number of values we have to support later.

After giving it some thought, I revise a little bit my opinion:

I think that if the value is evaluated to TRUE or FALSE, then fine. If it 
is anything else, then an error is raised (error message shown), which 
should also stop the script on "ON_ERROR_STOP", and if not the script 
continues with assuming the value was FALSE.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 18:08  Daniel Verite <daniel@manitou-mail.org>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 2 replies; 232+ messages in thread

From: Daniel Verite @ 2017-01-24 18:08 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

	Corey Huinker wrote:

> >
> > 1: unrecognized value "whatever" for "\if"; assuming "on"
> >
> > I do not think that the script should continue with such an assumption.
> >
> 
> I agree, and this means we can't use ParseVariableBool() as-is

The patch at https://commitfest.postgresql.org/12/799/
in the ongoing CF already changes ParseVariableBool()
to not assume that unrecognizable values should be set to
"on".

There's also the fact that ParseVariableBool() in HEAD assumes
that an empty value is valid and true, which I think leads to this
inconsistency in the current patch:

\set empty
\if :empty
select 1 as result \gset
\else
select 2 as result \gset
\endif
\echo 'result is' :result

produces: result is 1 (so an empty string evaluates to true)

Yet this sequence:

\if
select 1 as result \gset
\else
select 2 as result \gset
\endif
\echo 'result is' :result

produces: result is 2 (so an empty \if evaluates to false)

The equivalence between empty value and true in
ParseVariableBool() is also suppressed in the above-mentioned
patch.

ISTM that it's important that eventually ParseVariableBool()
and \if agree on what evaluates to true and false (and the
more straightforward way to achieve that is by \if calling
directly ParseVariableBool), but that it's not productive that we
discuss /if issues relatively to the behavior of ParseVariableBool()
in HEAD at the moment, as it's likely to change.


Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 18:25  Corey Huinker <corey.huinker@gmail.com>
  parent: Daniel Verite <daniel@manitou-mail.org>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-24 18:25 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

>
> ISTM that it's important that eventually ParseVariableBool()
> and \if agree on what evaluates to true and false (and the
> more straightforward way to achieve that is by \if calling
> directly ParseVariableBool), but that it's not productive that we
> discuss /if issues relatively to the behavior of ParseVariableBool()
> in HEAD at the moment, as it's likely to change.
>

I'd like to keep in sync with ParseVariableBoolean(), but

Also, Fabien has made a good case for invalid parsed values being an
ON_ERROR_STOP-able error, and not defaulted to either true or false.

This might be asking a lot, but could we make a "strict" mode for
ParseVariableBool() that returns a success boolean, and have the existing
ParseVariableBool() signature call that new function, and issue the
"assuming " warning if the strict function failed?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 18:40  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-01-24 18:40 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

On Tue, Jan 24, 2017 at 1:25 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> This might be asking a lot, but could we make a "strict" mode for
> ParseVariableBool() that returns a success boolean, and have the existing
> ParseVariableBool() signature call that new function, and issue the
> "assuming " warning if the strict function failed?


Nevermind. Looking at the v7 patch I see that it does everything I need and
more. I should have looked first.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 19:38  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Daniel Verite <daniel@manitou-mail.org>
  1 sibling, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-01-24 19:38 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

"Daniel Verite" <daniel@manitou-mail.org> writes:
> ISTM that it's important that eventually ParseVariableBool()
> and \if agree on what evaluates to true and false (and the
> more straightforward way to achieve that is by \if calling
> directly ParseVariableBool), but that it's not productive that we
> discuss /if issues relatively to the behavior of ParseVariableBool()
> in HEAD at the moment, as it's likely to change.

AFAIK we do have consensus on changing its behavior to disallow
assignment of invalid values.  It's just a matter of getting the
patch to be stylistically nice.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-24 19:52  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-24 19:52 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: pgsql-hackers

Revised patch, with one caveat: It contains copy/pasted code from
variable.c intended to bridge the gap until https://commitfest.postgresql.
org/12/799/  (changing ParseVariableBool to detect invalid boolean-ish
strings) is merged. We may want to pause full-review of this patch pending
resolution of that one. I'm happy to continue with the stop-gap in place.

Changes made:
- \elseif is now \elif
- Invalid boolean values now return an error
- ON_ERROR_STOP is respected in all errors raided by \if, \elsif, \else,
\endif commands.
- Documentation gives a more real-world example of usage.
- Documentation gives a more explicit list of valid boolean values
- Regression tests for out-of-place \endif, \else, and \endif
- Regression test for invalid boolean values
- Removal of debug detritus.

Changes not(yet) made:
- No TAP test for errors respecting ON_ERROR_STOP
- function comments in psqlscan.l follow the style found in other comments
there, which goes counter to global style.


On Tue, Jan 24, 2017 at 3:58 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> I would suggest to assume false on everything else, and/or maybe to ignore
>>> the whole if/endif section in such cases.
>>>
>>
>> +1, it also halves the number of values we have to support later.
>>
>
> After giving it some thought, I revise a little bit my opinion:
>
>
> I think that if the value is evaluated to TRUE or FALSE, then fine. If it
> is anything else, then an error is raised (error message shown), which
> should also stop the script on "ON_ERROR_STOP", and if not the script
> continues with assuming the value was FALSE.
>
> --
> Fabien.
>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 9915731..20091e5 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2007,6 +2007,78 @@ 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) as has_customers,
+    EXISTS(SELECT 1 FROM employee) as has_employees
+\gset
+\if :has_users
+    SELECT * FROM customer ORDER BY creation_date LIMIT 5;
+\elif :has_employees
+    \echo 'no customers found'
+    SELECT * FROM employee ORDER BY creation_date LIMIT 5;
+\else
+    \if yes
+        \echo 'No customers or employees'
+    \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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </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 is evaluated like other options booleans, so the valid values
+        are any unabiguous 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>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 4139b77..feb9ddc 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 && psqlscan_branch_active(scan_state))
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,68 @@ 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)
+{
+	bool	success = false;
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	/*
+	 * placeholder code until ParseVariableBool() ads error detection
+	 * once that patch is in place, use that instead
+	 */
+	if (value)
+	{
+		size_t		len;
+
+		if (value == NULL)
+			return false;			/* not set -> assume "off" */
+
+		len = strlen(value);
+
+		if ((pg_strncasecmp(value, "true", len) == 0) ||
+			(pg_strncasecmp(value, "yes", len) == 0) ||
+			(pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) ||
+			(pg_strcasecmp(value, "1") == 0))
+		{
+			success = true;
+			*result = true;
+		}
+		else if ((pg_strncasecmp(value, "false", len) == 0) ||
+				 (pg_strncasecmp(value, "no", len) == 0) ||
+				 (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) ||
+				 (pg_strcasecmp(value, "0") == 0))
+		{
+			success = true;
+			*result = false;
+		}
+		else
+		{
+			psql_error("\\%s: invalid boolean expression: %s\n",
+						action, value);
+		}
+		free(value);
+	}
+	else
+	{
+		psql_error("\\%s: no expression given\n",action);
+	}
+	return success;
+}
+
+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 +270,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!psqlscan_branch_active(scan_state) && !is_branching_command(cmd) )
+	{
+		/* 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.
@@ -984,6 +1055,114 @@ exec_command(const char *cmd,
 		}
 	}
 
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		if (psqlscan_branch_active(scan_state))
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "if", &if_true);
+			if (success)
+			{
+				if (if_true)
+					new_if_state = IFSTATE_TRUE;
+				else
+					new_if_state = IFSTATE_FALSE;
+			}
+		}
+		if (success)
+			psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		if (psqlscan_branch_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 */
+					break;
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					break;
+				case IFSTATE_FALSE:
+					/* determine if this section is true or not */
+					success = read_boolean_expression(scan_state, "elif",
+														&elif_true);
+					if (success)
+					{
+						if (elif_true)
+							psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					}
+					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);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\else\n");
+			success = false;
+		}
+		else
+		{
+			switch (psqlscan_branch_get_state(scan_state))
+			{
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+				case IFSTATE_IGNORED:
+					/* whole branch was inactive */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+					break;
+				case IFSTATE_FALSE:
+					/* just finished true section of active branch */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+					break;
+				case IFSTATE_ELSE_TRUE:
+				case IFSTATE_ELSE_FALSE:
+					psql_error("encountered \\else after \\else\n");
+					success = false;
+					break;
+				default:
+					break;
+			}
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+		}
+		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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..7252824 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (psqlscan_branch_active(scan_state))
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,15 +367,21 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (psqlscan_branch_active(scan_state))
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +440,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (psqlscan_branch_active(scan_state))
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +471,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..f70841c 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+	state->branch_block_active = true;
+
 	return state;
 }
 
@@ -919,6 +922,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 +1436,103 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_empty(PsqlScanState state)
+{
+	return (state->branch_stack == NULL);
+}
+
+/*
+ * 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)
+{
+	return state->branch_block_active;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_update_active
+ *
+ * Scan the branch_stack to determine whether the next statements
+ * can execute or should be skipped. Cache this result in
+ * branch_block_active.
+ */
+static void
+psqlscan_branch_update_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	state->branch_block_active  = ( (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;
+	psqlscan_branch_update_active(state);
+	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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	psqlscan_branch_update_active(state);
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	psqlscan_branch_update_active(state);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..734e719 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,12 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
+	bool branch_block_active;
 } PsqlScanStateData;
 
 
@@ -141,4 +171,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..1dcaa46 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,66 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 900aa7e..4f41894 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,66 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\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

Attachments:

  [text/plain] 0001.if_endif.v2.diff (20.4K, ../../CADkLM=eLeC+xpKsqE92etfhnQRw83YM42QFAm+PtQTDso-bPow@mail.gmail.com/3-0001.if_endif.v2.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 9915731..20091e5 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2007,6 +2007,78 @@ 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) as has_customers,
+    EXISTS(SELECT 1 FROM employee) as has_employees
+\gset
+\if :has_users
+    SELECT * FROM customer ORDER BY creation_date LIMIT 5;
+\elif :has_employees
+    \echo 'no customers found'
+    SELECT * FROM employee ORDER BY creation_date LIMIT 5;
+\else
+    \if yes
+        \echo 'No customers or employees'
+    \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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </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 is evaluated like other options booleans, so the valid values
+        are any unabiguous 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>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 4139b77..feb9ddc 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 && psqlscan_branch_active(scan_state))
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,68 @@ 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)
+{
+	bool	success = false;
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	/*
+	 * placeholder code until ParseVariableBool() ads error detection
+	 * once that patch is in place, use that instead
+	 */
+	if (value)
+	{
+		size_t		len;
+
+		if (value == NULL)
+			return false;			/* not set -> assume "off" */
+
+		len = strlen(value);
+
+		if ((pg_strncasecmp(value, "true", len) == 0) ||
+			(pg_strncasecmp(value, "yes", len) == 0) ||
+			(pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) ||
+			(pg_strcasecmp(value, "1") == 0))
+		{
+			success = true;
+			*result = true;
+		}
+		else if ((pg_strncasecmp(value, "false", len) == 0) ||
+				 (pg_strncasecmp(value, "no", len) == 0) ||
+				 (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) ||
+				 (pg_strcasecmp(value, "0") == 0))
+		{
+			success = true;
+			*result = false;
+		}
+		else
+		{
+			psql_error("\\%s: invalid boolean expression: %s\n",
+						action, value);
+		}
+		free(value);
+	}
+	else
+	{
+		psql_error("\\%s: no expression given\n",action);
+	}
+	return success;
+}
+
+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 +270,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!psqlscan_branch_active(scan_state) && !is_branching_command(cmd) )
+	{
+		/* 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.
@@ -984,6 +1055,114 @@ exec_command(const char *cmd,
 		}
 	}
 
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		if (psqlscan_branch_active(scan_state))
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "if", &if_true);
+			if (success)
+			{
+				if (if_true)
+					new_if_state = IFSTATE_TRUE;
+				else
+					new_if_state = IFSTATE_FALSE;
+			}
+		}
+		if (success)
+			psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		if (psqlscan_branch_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 */
+					break;
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					break;
+				case IFSTATE_FALSE:
+					/* determine if this section is true or not */
+					success = read_boolean_expression(scan_state, "elif",
+														&elif_true);
+					if (success)
+					{
+						if (elif_true)
+							psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					}
+					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);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\else\n");
+			success = false;
+		}
+		else
+		{
+			switch (psqlscan_branch_get_state(scan_state))
+			{
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+				case IFSTATE_IGNORED:
+					/* whole branch was inactive */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+					break;
+				case IFSTATE_FALSE:
+					/* just finished true section of active branch */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+					break;
+				case IFSTATE_ELSE_TRUE:
+				case IFSTATE_ELSE_FALSE:
+					psql_error("encountered \\else after \\else\n");
+					success = false;
+					break;
+				default:
+					break;
+			}
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+		}
+		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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..7252824 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (psqlscan_branch_active(scan_state))
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,15 +367,21 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (psqlscan_branch_active(scan_state))
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +440,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (psqlscan_branch_active(scan_state))
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +471,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..f70841c 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+	state->branch_block_active = true;
+
 	return state;
 }
 
@@ -919,6 +922,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 +1436,103 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_empty(PsqlScanState state)
+{
+	return (state->branch_stack == NULL);
+}
+
+/*
+ * 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)
+{
+	return state->branch_block_active;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_update_active
+ *
+ * Scan the branch_stack to determine whether the next statements
+ * can execute or should be skipped. Cache this result in
+ * branch_block_active.
+ */
+static void
+psqlscan_branch_update_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	state->branch_block_active  = ( (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;
+	psqlscan_branch_update_active(state);
+	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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	psqlscan_branch_update_active(state);
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	psqlscan_branch_update_active(state);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..734e719 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,12 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
+	bool branch_block_active;
 } PsqlScanStateData;
 
 
@@ -141,4 +171,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..1dcaa46 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,66 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 900aa7e..4f41894 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,66 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-26 20:15  Daniel Verite <daniel@manitou-mail.org>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-01-26 20:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

	Corey Huinker wrote:

> Revised patch

A comment about control flow and variables:
in branches that are not taken, variables are expanded 
nonetheless, in a way that can be surprising.
Case in point:

\set var 'ab''cd'
-- select :var; 
\if false
  select :var ;
\else
  select 1;
\endif

The 2nd reference to :var has a quoting hazard, but since it's within
an "\if false" branch, at a glance it seems like this script might work.
In fact it barfs with:
  psql:script.sql:0: found EOF before closing \endif(s)

AFAICS what happens is that :var gets injected and starts a
runaway string, so that as far as the parser is concerned
the \else ..\endif block slips into the untaken branch, as a part of
that unfinished string.

This contrasts with line 2: -- select :var
where the reference to :var is inoffensive.

To avoid that kind of trouble, would it make sense not to expand
variables in untaken branches?

Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-26 20:55  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-26 20:55 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers


Hello Daniel,

> A comment about control flow and variables: in branches that are not 
> taken, variables are expanded nonetheless, in a way that can be 
> surprising. Case in point:
>
> \set var 'ab''cd'
> -- select :var;
> \if false
>  select :var ;
> \else
>  select 1;
> \endif
>
> To avoid that kind of trouble, would it make sense not to expand
> variables in untaken branches?

Hmmm. This case is somehow contrived (for a string, :'var' could be used, 
in which case escaping would avoid the hazard), but I think that what you 
suggest is a better behavior, if easy to implement.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-26 21:06  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-26 21:06 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

On Thu, Jan 26, 2017 at 3:55 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Daniel,
>
> A comment about control flow and variables: in branches that are not
>> taken, variables are expanded nonetheless, in a way that can be surprising.
>> Case in point:
>>
>> \set var 'ab''cd'
>> -- select :var;
>> \if false
>>  select :var ;
>> \else
>>  select 1;
>> \endif
>>
>> To avoid that kind of trouble, would it make sense not to expand
>> variables in untaken branches?
>>
>
> Hmmm. This case is somehow contrived (for a string, :'var' could be used,
> in which case escaping would avoid the hazard), but I think that what you
> suggest is a better behavior, if easy to implement.
>
> --
> Fabien.
>

Good question, Daniel. Variable expansion seems to be done via
psql_get_variable which is invoked via callback, which means that I might
have to move branch_block_active into PsqlSettings. That's slightly
different because the existing boolean is scoped with MainLoop(), but
there's no way to get to a new MainLoop unless you're in an executing
branch, and no way to legally exit a MainLoop with an unbalanced if-endif
state. In short, I think it's better behavior. It does prevent someone from
setting a variable to '\endif' and expecting that to work, like this:

select case
     when random() < 0.5 then '\endif'
     else E'\else\n\echo fooled you\n\endif'
end as contrived_metaprogramming
\gset

\if false
  :contrived_metaprogramming


I'm sure someone will argue that preventing that is a good thing. Unless
someone sees a good reason not to move that PsqlSettings, I'll make that
change.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-27 22:43  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-27 22:43 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

On Thu, Jan 26, 2017 at 4:06 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

>
>
> On Thu, Jan 26, 2017 at 3:55 PM, Fabien COELHO <coelho@cri.ensmp.fr>
> wrote:
>
>>
>> Hello Daniel,
>>
>> A comment about control flow and variables: in branches that are not
>>> taken, variables are expanded nonetheless, in a way that can be surprising.
>>> Case in point:
>>>
>>> \set var 'ab''cd'
>>> -- select :var;
>>> \if false
>>>  select :var ;
>>> \else
>>>  select 1;
>>> \endif
>>>
>>> To avoid that kind of trouble, would it make sense not to expand
>>> variables in untaken branches?
>>>
>>
>> Hmmm. This case is somehow contrived (for a string, :'var' could be used,
>> in which case escaping would avoid the hazard), but I think that what you
>> suggest is a better behavior, if easy to implement.
>>
>> --
>> Fabien.
>>
>
> Good question, Daniel. Variable expansion seems to be done via
> psql_get_variable which is invoked via callback, which means that I might
> have to move branch_block_active into PsqlSettings. That's slightly
> different because the existing boolean is scoped with MainLoop(), but
> there's no way to get to a new MainLoop unless you're in an executing
> branch, and no way to legally exit a MainLoop with an unbalanced if-endif
> state. In short, I think it's better behavior. It does prevent someone from
> setting a variable to '\endif' and expecting that to work, like this:
>
> select case
>      when random() < 0.5 then '\endif'
>      else E'\else\n\echo fooled you\n\endif'
> end as contrived_metaprogramming
> \gset
>
> \if false
>   :contrived_metaprogramming
>
>
> I'm sure someone will argue that preventing that is a good thing. Unless
> someone sees a good reason not to move that PsqlSettings, I'll make that
> change.
>
>
And here it is
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 640fe12..fcf265b 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,78 @@ 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) as has_customers,
+    EXISTS(SELECT 1 FROM employee) as has_employees
+\gset
+\if :has_users
+    SELECT * FROM customer ORDER BY creation_date LIMIT 5;
+\elif :has_employees
+    \echo 'no customers found'
+    SELECT * FROM employee ORDER BY creation_date LIMIT 5;
+\else
+    \if yes
+        \echo 'No customers or employees'
+    \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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </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 is evaluated like other options booleans, so the valid values
+        are any unabiguous 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>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 0c164a3..8235015 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,68 @@ 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)
+{
+	bool	success = false;
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	/*
+	 * placeholder code until ParseVariableBool() ads error detection
+	 * once that patch is in place, use that instead
+	 */
+	if (value)
+	{
+		size_t		len;
+
+		if (value == NULL)
+			return false;			/* not set -> assume "off" */
+
+		len = strlen(value);
+
+		if ((pg_strncasecmp(value, "true", len) == 0) ||
+			(pg_strncasecmp(value, "yes", len) == 0) ||
+			(pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) ||
+			(pg_strcasecmp(value, "1") == 0))
+		{
+			success = true;
+			*result = true;
+		}
+		else if ((pg_strncasecmp(value, "false", len) == 0) ||
+				 (pg_strncasecmp(value, "no", len) == 0) ||
+				 (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) ||
+				 (pg_strcasecmp(value, "0") == 0))
+		{
+			success = true;
+			*result = false;
+		}
+		else
+		{
+			psql_error("\\%s: invalid boolean expression: %s\n",
+						action, value);
+		}
+		free(value);
+	}
+	else
+	{
+		psql_error("\\%s: no expression given\n",action);
+	}
+	return success;
+}
+
+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 +270,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd) )
+	{
+		/* 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.
@@ -1000,6 +1071,141 @@ exec_command(const char *cmd,
 		}
 	}
 
+	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", &if_true);
+			if (success)
+			{
+				if (if_true)
+					new_if_state = IFSTATE_TRUE;
+				else
+					new_if_state = IFSTATE_FALSE;
+			}
+		}
+		if (success)
+		{
+			psqlscan_branch_push(scan_state,new_if_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		if (psqlscan_branch_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.
+					 */
+					/* switch variable expansion back on just for this line */
+					pset.active_branch = true;
+					success = read_boolean_expression(scan_state, "elif",
+														&elif_true);
+					pset.active_branch = false;
+					if (success)
+					{
+						if (elif_true)
+						{
+							psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+							pset.active_branch = true;
+						}
+					}
+					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);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\else\n");
+			success = false;
+		}
+		else
+		{
+			switch (psqlscan_branch_get_state(scan_state))
+			{
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+				case IFSTATE_IGNORED:
+					/* whole branch was inactive */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+					pset.active_branch = false;
+					break;
+				case IFSTATE_FALSE:
+					/* just finished true section of 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;
+			}
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index e1b04de..a526008 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -126,6 +126,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables 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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..b8d4887 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (pset.active_branch)
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,15 +367,21 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (pset.active_branch)
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +440,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (pset.active_branch)
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +471,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 4c7c3b1..15d5599 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 85aac4a..b03ded4 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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..a09d404 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,88 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_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_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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(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..a24a5ac 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_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..5cc1130 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,78 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 900aa7e..8793aa2 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,74 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\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

Attachments:

  [text/plain] 0001.if_endif.v3.diff (22.0K, ../../CADkLM=di=8WQRQDku6mfN=-pBqVQq_3osAARtVb6Tf0U8vyb7A@mail.gmail.com/3-0001.if_endif.v3.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 640fe12..fcf265b 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,78 @@ 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) as has_customers,
+    EXISTS(SELECT 1 FROM employee) as has_employees
+\gset
+\if :has_users
+    SELECT * FROM customer ORDER BY creation_date LIMIT 5;
+\elif :has_employees
+    \echo 'no customers found'
+    SELECT * FROM employee ORDER BY creation_date LIMIT 5;
+\else
+    \if yes
+        \echo 'No customers or employees'
+    \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 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </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 is evaluated like other options booleans, so the valid values
+        are any unabiguous 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>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elif</command> command cannot follow the
+        <command>\else</command> command.
+        </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/command.c b/src/bin/psql/command.c
index 0c164a3..8235015 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,68 @@ 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)
+{
+	bool	success = false;
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	/*
+	 * placeholder code until ParseVariableBool() ads error detection
+	 * once that patch is in place, use that instead
+	 */
+	if (value)
+	{
+		size_t		len;
+
+		if (value == NULL)
+			return false;			/* not set -> assume "off" */
+
+		len = strlen(value);
+
+		if ((pg_strncasecmp(value, "true", len) == 0) ||
+			(pg_strncasecmp(value, "yes", len) == 0) ||
+			(pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) ||
+			(pg_strcasecmp(value, "1") == 0))
+		{
+			success = true;
+			*result = true;
+		}
+		else if ((pg_strncasecmp(value, "false", len) == 0) ||
+				 (pg_strncasecmp(value, "no", len) == 0) ||
+				 (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) ||
+				 (pg_strcasecmp(value, "0") == 0))
+		{
+			success = true;
+			*result = false;
+		}
+		else
+		{
+			psql_error("\\%s: invalid boolean expression: %s\n",
+						action, value);
+		}
+		free(value);
+	}
+	else
+	{
+		psql_error("\\%s: no expression given\n",action);
+	}
+	return success;
+}
+
+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 +270,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd) )
+	{
+		/* 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.
@@ -1000,6 +1071,141 @@ exec_command(const char *cmd,
 		}
 	}
 
+	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", &if_true);
+			if (success)
+			{
+				if (if_true)
+					new_if_state = IFSTATE_TRUE;
+				else
+					new_if_state = IFSTATE_FALSE;
+			}
+		}
+		if (success)
+		{
+			psqlscan_branch_push(scan_state,new_if_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		if (psqlscan_branch_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.
+					 */
+					/* switch variable expansion back on just for this line */
+					pset.active_branch = true;
+					success = read_boolean_expression(scan_state, "elif",
+														&elif_true);
+					pset.active_branch = false;
+					if (success)
+					{
+						if (elif_true)
+						{
+							psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+							pset.active_branch = true;
+						}
+					}
+					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);
+	}
+
+	else if (strcmp(cmd, "else") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\else\n");
+			success = false;
+		}
+		else
+		{
+			switch (psqlscan_branch_get_state(scan_state))
+			{
+				case IFSTATE_TRUE:
+					/* just finished true section of active branch */
+				case IFSTATE_IGNORED:
+					/* whole branch was inactive */
+					psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+					pset.active_branch = false;
+					break;
+				case IFSTATE_FALSE:
+					/* just finished true section of 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;
+			}
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index e1b04de..a526008 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -126,6 +126,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables 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/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..b8d4887 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
 	volatile int count_eof = 0;
 	volatile bool die_on_error = false;
 
+	/* only needed at the end to detect unbalanced ifs in scan_state */
+	bool if_endifs_balanced = true;
+
 	/* Save the prior command source */
 	FILE	   *prev_cmd_source;
 	bool		prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
 			if (scan_result == PSCAN_SEMICOLON ||
 				(scan_result == PSCAN_EOL && pset.singleline))
 			{
-				/*
-				 * Save query in history.  We use history_buf to accumulate
-				 * multi-line queries into a single history entry.
-				 */
-				if (pset.cur_cmd_interactive && !line_saved_in_history)
+				if (pset.active_branch)
 				{
-					pg_append_history(line, history_buf);
-					pg_send_history(history_buf);
-					line_saved_in_history = true;
+					/*
+					 * Save query in history.  We use history_buf to accumulate
+					 * multi-line queries into a single history entry.
+					 */
+					if (pset.cur_cmd_interactive && !line_saved_in_history)
+					{
+						pg_append_history(line, history_buf);
+						pg_send_history(history_buf);
+						line_saved_in_history = true;
+					}
+
+					/* execute query */
+					success = SendQuery(query_buf->data);
 				}
+				else
+					success = true;
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,15 +367,21 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
-
-					/* transfer query to previous_buf by pointer-swapping */
+					if (pset.active_branch)
 					{
-						PQExpBuffer swap_buf = previous_buf;
+						success = SendQuery(query_buf->data);
 
-						previous_buf = query_buf;
-						query_buf = swap_buf;
+						/* transfer query to previous_buf by pointer-swapping */
+						{
+							PQExpBuffer swap_buf = previous_buf;
+
+							previous_buf = query_buf;
+							query_buf = swap_buf;
+						}
 					}
+					else
+						success = true;
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -425,12 +440,17 @@ MainLoop(FILE *source)
 	if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
 		successResult == EXIT_SUCCESS)
 	{
-		/* save query in history */
-		if (pset.cur_cmd_interactive)
-			pg_send_history(history_buf);
+		if (pset.active_branch)
+		{
+			/* save query in history */
+			if (pset.cur_cmd_interactive)
+				pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+			/* execute query */
+			success = SendQuery(query_buf->data);
+		}
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,11 +471,17 @@ MainLoop(FILE *source)
 	destroyPQExpBuffer(previous_buf);
 	destroyPQExpBuffer(history_buf);
 
+	if (slashCmdStatus != PSQL_CMD_TERMINATE)
+		if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
 	psql_scan_destroy(scan_state);
 
 	pset.cur_cmd_source = prev_cmd_source;
 	pset.cur_cmd_interactive = prev_cmd_interactive;
 	pset.lineno = prev_lineno;
 
+	if (! if_endifs_balanced )
+		psql_error("found EOF before closing \\endif(s)\n");
+
 	return successResult;
 }	/* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 4c7c3b1..15d5599 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 85aac4a..b03ded4 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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..a09d404 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,88 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_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_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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(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..a24a5ac 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_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 464436a..5cc1130 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,78 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 900aa7e..8793aa2 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,74 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-28 07:38  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-28 07:38 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers


Hello Corey,

> And here it is

About the patch v3:

## DOCUMENTATION

I'm wondering what pg would do on "EXISTS(SELECT 1 FROM customer)" if 
there are many employees. EXPLAIN suggests a seq_scan, which seems bad. 
With "(SELECT COUNT(*) FROM pgbench_accounts) <> 0" pg seems to generate 
an index only scan on a large table, so maybe it is a better way to do it. 
It seems strange that there is no better way to do that in a relational 
tool, the relational model being an extension of set theory... and there 
is no easy way (?) to check whether a set is empty.

"""If an EOF is reached on the main file or an 
<command>\include</command>-ed file before all 
<command>\if</command>-<command>\endif</command> are matched, then psql 
will raise an error."""

In sentence above: "before all" -> "before all local"? "then" -> ""?

"other options booleans" -> "other booleans of options"? or "options' 
booleans" maybe, but that is for people?

"unabigous" -> "unambiguous"

I think that the three paragraph explanation about non evaluation could be 
factor into one, maybe something like: """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 nesting."""

I would suggest to merge elif/else constraints by describing what is 
expected rather than what is not expected. """An \if command may contain 
any number of \elif clauses and may end with one \else clause""".


## CODE

In "read_boolean_expression":

  + if (value)

"if (value != NULL)" is usually used, I think.

  + if (value == NULL)
  +   return false; /* not set -> assume "off" */

This is dead code, because value has been checked to be non NULL a few 
lines above.

  + (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
  + (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)

Hmmm, not easy to parse. Maybe it deserves a comment?
"check at least two chars to distinguish on & off"

",action" -> ", action" (space after commas).

The "result" is not set on errors, but maybe it should be set to false 
anyway and explicitely, instead of relying on some prior initialization?
Or document that the result is not set on errors.

if command:

   if (is active) {
     success = ...
     if (success) {
       ...
     }
   }
   if (success) {
     ...
   }

The second test on success may not rely on the value set above. That looks 
very strange. ISTM that the state should be pushed whether parsing 
succeeded or not. Moreover, it results in:

   \if ERROR
      \echo X
   \else
      \echo Y
   \endif

having both X & Y printed and error reported on else and endif. I think 
that an expression error should just put the stuff in ignore state.


On "else" when in state ignored, ISTM that it should remain in state 
ignore, not switch to else-false.


Comment about "IFSTATE_FALSE" talks about the state being true, maybe a 
copy-paste error?

In comments: "... variables the branch" -> "variables if the branch"

The "if_endifs_balanced" variable is not very useful. Maybe just the test 
and error reporting in the right place:

  if (... && !psqlscan_branch_empty(scan_state))
    psql_error("found EOF before closing \\endif(s)\n");



  +
   #endif   /* MAINLOOP_H */


  -
  /*
   * Main processing loop for reading lines of input
   *     and sending them to the backend.

Do not add/remove empty lines in places unrelated to the patch?


History saving: I'm wondering whether all read line should be put into 
history, whether executed or not.

Is it possible to make some of the added functions static? If so, do it.

I checked that it does stop on errors with -v ON_ERROR_STOP=1. However I 
would be more at ease if this was tested somewhere.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-28 17:46  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-01-28 17:46 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

>
>
> I'm wondering what pg would do on "EXISTS(SELECT 1 FROM customer)" if
> there are many employees. EXPLAIN suggests a seq_scan, which seems bad.
> With "(SELECT COUNT(*) FROM pgbench_accounts) <> 0" pg seems to generate an
> index only scan on a large table, so maybe it is a better way to do it. It
> seems strange that there is no better way to do that in a relational tool,
> the relational model being an extension of set theory... and there is no
> easy way (?) to check whether a set is empty.
>

I believe that the scan stops on the first row it finds, because the
EXITS() clause is met. But it's not relevant to the documentation, I simply
wanted to demonstrate some results that couldn't be resolved at parse time,
so that the \if tests were meaningful. If the query example is distracting
from the point of the documentation, we should change it.


>
> """If an EOF is reached on the main file or an
> <command>\include</command>-ed file before all
> <command>\if</command>-<command>\endif</command> are matched, then psql
> will raise an error."""
>
> In sentence above: "before all" -> "before all local"? "then" -> ""?
>

+1

>
> "other options booleans" -> "other booleans of options"? or "options'
> booleans" maybe, but that is for people?
>

+1

>
> "unabigous" -> "unambiguous"
>

+1


>
> I think that the three paragraph explanation about non evaluation could be
> factor into one, maybe something like: """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 nesting."""
>
> I would suggest to merge elif/else constraints by describing what is
> expected rather than what is not expected. """An \if command may contain
> any number of \elif clauses and may end with one \else clause""".
>

I'll give it another shot, as I forgot to mention the non-evaluation of
expressions in dead branches.



>
>
> ## CODE
>
> In "read_boolean_expression":
>
>  + if (value)
>
> "if (value != NULL)" is usually used, I think.
>
>  + if (value == NULL)
>  +   return false; /* not set -> assume "off" */
>
> This is dead code, because value has been checked to be non NULL a few
> lines above.
>
>  + (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
>  + (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
>
> Hmmm, not easy to parse. Maybe it deserves a comment?
> "check at least two chars to distinguish on & off"
>
> ",action" -> ", action" (space after commas).
>
> The "result" is not set on errors, but maybe it should be set to false
> anyway and explicitely, instead of relying on some prior initialization?
> Or document that the result is not set on errors.
>

This is code lifted from variable.c's ParseVariableBool().  When the other
patch for "psql hooks" is committed (the one that detects when the string
wasn't a valid boolean), this code will go away and we'll just use
ParseVariableBool() again.


>
> if command:
>
>   if (is active) {
>     success = ...
>     if (success) {
>       ...
>     }
>   }
>   if (success) {
>     ...
>   }
>
> The second test on success may not rely on the value set above. That looks
> very strange. ISTM that the state should be pushed whether parsing
> succeeded or not. Moreover, it results in:
>
>   \if ERROR
>      \echo X
>   \else
>      \echo Y
>   \endif
>
> having both X & Y printed and error reported on else and endif. I think
> that an expression error should just put the stuff in ignore state.
>

Not just false, but ignore the whole if-endif? interesting. I hadn't
thought of that. Can do.


>
>
> On "else" when in state ignored, ISTM that it should remain in state
> ignore, not switch to else-false.
>

That's how I know if this is the first "else" I encountered. I could split
the if-state back into a struct of booleans if you think that makes more
sense.



>
>
> Comment about "IFSTATE_FALSE" talks about the state being true, maybe a
> copy-paste error?
>

Yes.


>
> In comments: "... variables the branch" -> "variables if the branch"
>

Yes.


>
> The "if_endifs_balanced" variable is not very useful. Maybe just the test
> and error reporting in the right place:
>
>  if (... && !psqlscan_branch_empty(scan_state))
>    psql_error("found EOF before closing \\endif(s)\n");
>

+1
I think I got the idea at some point that psql_error broke out of the
current execution block.


> History saving: I'm wondering whether all read line should be put into
> history, whether executed or not.
>

Good question. I gave it some thought and I decided it shouldn't.  First,
because history is a set of statements that were attempted, and those
statements were not. But perhaps more importantly, because the statement
could have contained an expandable variable, and since that variable would
not be evaluated the SQL stored would be subtly altered from the original
intent, perhaps in ways that might drastically alter the meaning of the
statement. A highly contrived example:

\set clause 'where cust_id = 1'
\if false
delete from customers :clause;
\endif


So yeah, it just seemed easier to not store in history.


> Is it possible to make some of the added functions static? If so, do it.
>

I try to. I think some of the functions that used to be called in
mainloop.c or command.c might not be anymore, and those can be made static.
I'll recheck which ones can be.


> I checked that it does stop on errors with -v ON_ERROR_STOP=1. However I
> would be more at ease if this was tested somewhere.


Yes, TAP tests forthcoming. I'll probably put out one more intermediate
patch to get the 'just-ignore-til-endif' functionality of an invalid \if or
\elseif, but the final push for a committed patch will have to wait until
after the ParseVariableBool() issue is worked out.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-29 08:35  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 3 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-01-29 08:35 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers


Hello,

>> I'm wondering what pg would do on "EXISTS(SELECT 1 FROM customer)" if
>> there are many employees. [...]
>
> I believe that the scan stops on the first row it finds, because the
> EXITS() clause is met.

Hmmm... That is not so clear from "EXPLAIN" output:

  Result  (cost=0.03..0.04 rows=1 width=1)
   InitPlan 1 (returns $0)
   ->  Seq Scan on ...  (cost=0.00..263981.69 rows=10001769 width=0)

There is a plan for the sub-query, so it looks like it is actually fully 
executed. Maybe adding "LIMIT 1" would be better?

> But it's not relevant to the documentation, I simply wanted to 
> demonstrate some results that couldn't be resolved at parse time, so 
> that the \if tests were meaningful. If the query example is distracting 
> from the point of the documentation, we should change it.

My point is that examples about one thing can be interpreted as example 
for other things which is also done in the example, so it is better to do 
everything right.


>> In "read_boolean_expression": [...]

> This is code lifted from variable.c's ParseVariableBool().  When the other
> patch for "psql hooks" is committed (the one that detects when the string
> wasn't a valid boolean), this code will go away and we'll just use
> ParseVariableBool() again.

Hmmm. Copy-pasting is bad enough, and "when the other patch is committed" 
is an unknown, so I would still suggest to fix obvious defects at least 
(eg dead code which may result in compiler warnings, inconsistent 
comments...).

>> [...] The second test on success may not rely on the value set above. 
>> That looks very strange. ISTM that the state should be pushed whether 
>> parsing succeeded or not. Moreover, it results in:
>>
>>   \if ERROR
>>      \echo X
>>   \else
>>      \echo Y
>>   \endif
>>
>> having both X & Y printed and error reported on else and endif. I think
>> that an expression error should just put the stuff in ignore state.
>
> Not just false, but ignore the whole if-endif? interesting. I hadn't
> thought of that. Can do.

My point was that you must at least push something, otherwise both 
branches are executed (!), and some commands could be attached to 
upper-level conditions:

   \if true
     \if ERROR
       ...
     \endif // this becomes "if true \endif"
     ...
   \endif // this becomes an error

As for which state is pushed, it is indeed debatable. I do think that 
pushing ignore on errors is a better/less risky behavior, but other 
people' opinion may differ.

>> On "else" when in state ignored, ISTM that it should remain in state
>> ignore, not switch to else-false.
>
> That's how I know if this is the first "else" I encountered.

Ok, my mistake. Maybe expand the comment a little bit if appropriate.

>> History saving: I'm wondering whether all read line should be put into
>> history, whether executed or not.
>
> Good question. I gave it some thought and I decided it shouldn't.  First,
> because history is a set of statements that were attempted, and those
> statements were not. But perhaps more importantly, because the statement
> could have contained an expandable variable, and since that variable would
> not be evaluated the SQL stored would be subtly altered from the original
> intent, perhaps in ways that might drastically alter the meaning of the
> statement. A highly contrived example:
>
> \set clause 'where cust_id = 1'
> \if false
> delete from customers :clause;
> \endif

Hmmm.

> So yeah, it just seemed easier to not store in history.

Hmmm.

As I recall, history is only for interactive mode. If I really typed 
something, I'm expecting to get it by visiting previous commands, because 
I certainly do not want to retype it again.

For your above example, maybe I would reedit the clause definition, 
then want to execute the delete.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-29 20:40  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-01-29 20:40 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

>
> My point is that examples about one thing can be interpreted as example
> for other things which is also done in the example, so it is better to do
> everything right.
>

Fair enough. I'll rewrite the examples to use pk lookups. I doubt the query
plan for those will change much in the future.


> Hmmm. Copy-pasting is bad enough, and "when the other patch is committed"
> is an unknown, so I would still suggest to fix obvious defects at least (eg
> dead code which may result in compiler warnings, inconsistent comments...).
>

It was do that or pause this work until that unknown was resolved.



>
> My point was that you must at least push something, otherwise both
> branches are executed (!), and some commands could be attached to
> upper-level conditions:
>
>
> As for which state is pushed, it is indeed debatable. I do think that
> pushing ignore on errors is a better/less risky behavior, but other people'
> opinion may differ.


+1




>
>
> On "else" when in state ignored, ISTM that it should remain in state
>>> ignore, not switch to else-false.
>>>
>>
>> That's how I know if this is the first "else" I encountered.
>>
>
> Ok, my mistake. Maybe expand the comment a little bit if appropriate.


+1


>
> As I recall, history is only for interactive mode. If I really typed
> something, I'm expecting to get it by visiting previous commands, because I
> certainly do not want to retype it again.
>
> For your above example, maybe I would reedit the clause definition, then
> want to execute the delete.


Good points, and history does save the string with the variable in it, not
the resolved string that was sent (or not sent) to the server.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-29 21:49  Jim Nasby <Jim.Nasby@BlueTreble.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 0 replies; 232+ messages in thread

From: Jim Nasby @ 2017-01-29 21:49 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

On 1/29/17 2:35 AM, Fabien COELHO wrote:
>>> I'm wondering what pg would do on "EXISTS(SELECT 1 FROM customer)" if
>>> there are many employees. [...]
>>
>> I believe that the scan stops on the first row it finds, because the
>> EXITS() clause is met.
>
> Hmmm... That is not so clear from "EXPLAIN" output:

You need to use a better test case...

> explain analyze select exists(select 1 from generate_series(1,99999) gs);
>                                                           QUERY PLAN
> -------------------------------------------------------------------------------------------------------------------------------
>  Result  (cost=0.01..0.02 rows=1 width=1) (actual time=26.278..26.278 rows=1 loops=1)
>    InitPlan 1 (returns $0)
>      ->  Function Scan on generate_series gs  (cost=0.00..10.00 rows=1000 width=0) (actual time=26.271..26.271 rows=1 loops=1)
>  Planning time: 6.568 ms
>  Execution time: 48.917 ms
> (5 rows)

In any case, +1 for not promoting count(*) <> 0; that's a really, really 
bad way to test for existence.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-30 16:57  Daniel Verite <daniel@manitou-mail.org>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-01-30 16:57 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers

	Corey Huinker wrote:

> >   \if ERROR
> >      \echo X
> >   \else
> >      \echo Y
> >   \endif
> >
> > having both X & Y printed and error reported on else and endif. I think
> > that an expression error should just put the stuff in ignore state.
> >
> 
> Not just false, but ignore the whole if-endif? interesting. I hadn't
> thought of that. Can do.

If we use the Unix shell as a model, in POSIX "test" and  "if"
are such that an evaluation error (exit status>1) leads to the same
flow than when evaluating to false (exit status=1).

References I can find:

test:
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html

if:
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_04_07

BTW, in "set -e" mode, it also says that a failure to evaluate an "if"
 expression does not lead to the script stopping:
<quote>
  The -e setting shall be ignored when executing the compound list
  following the while, until, if, or elif reserved word, a pipeline
  beginning with the ! reserved word, or any command of an AND-OR list
  other than the last
</quote>

So psql is not following that model with ON_ERROR_STOP if it exits
with an error when unable to evaluate an "if" expression.
I'm not implying that we should necessarily adopt the shell behavior,
but as these choices have certainly been made in POSIX for good
reasons, we should make sure to think twice about why they don't
apply to psql.


Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-30 17:35  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-01-30 17:35 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers


Hello Daniel,

> [...] So psql is not following that model with ON_ERROR_STOP if it exits 
> with an error when unable to evaluate an "if" expression. I'm not 
> implying that we should necessarily adopt the shell behavior, but as 
> these choices have certainly been made in POSIX for good reasons, we 
> should make sure to think twice about why they don't apply to psql.

Interesting point.

The shell is about processes, and if relies on the status code returned, 
with 0 meaning true, and anything else, which is somehow a process error, 
meaning false. So there is no way to distinguish false from process error 
in this system, because the status is just one integer.

However, a syntax error, for instance with a shell internal test, leads to 
nothing being executed:

    bash> if [[ bad syntax ]] ; then echo then ; else echo else ; fi
    -bash: conditional binary operator expected
    -bash: syntax error near `syntax'
    # nothing is echoed

Another example with python in interactive mode:

    python> if 1+: print 1; else print 0
    SyntaxError: invalid syntax
    # nothing is printed

So rejecting execution altogether on syntax errors is somehow a common 
practice.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-31 06:04  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-01-31 06:04 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers


>> This is code lifted from variable.c's ParseVariableBool().  When the other
>> patch for "psql hooks" is committed (the one that detects when the string
>> wasn't a valid boolean), this code will go away and we'll just use
>> ParseVariableBool() again.

The ParseVariableBool function has been updated, and the new version is 
much cleaner, including all fixes that I suggested in your copy, so you 
can use it in your patch.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-31 17:16  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-01-31 17:16 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

On Tue, Jan 31, 2017 at 1:04 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> This is code lifted from variable.c's ParseVariableBool().  When the other
>>> patch for "psql hooks" is committed (the one that detects when the string
>>> wasn't a valid boolean), this code will go away and we'll just use
>>> ParseVariableBool() again.
>>>
>>
> The ParseVariableBool function has been updated, and the new version is
> much cleaner, including all fixes that I suggested in your copy, so you can
> use it in your patch.
>
> --
> Fabien.
>

I see there's still a lot of activity in the thread, I can't tell if it's
directly related to ParseVariableBool() or in the way it is called. Should
I wait for the dust to settle over there?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-01-31 18:15  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-01-31 18:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

Corey Huinker <corey.huinker@gmail.com> writes:
> On Tue, Jan 31, 2017 at 1:04 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
>> The ParseVariableBool function has been updated, and the new version is
>> much cleaner, including all fixes that I suggested in your copy, so you can
>> use it in your patch.

> I see there's still a lot of activity in the thread, I can't tell if it's
> directly related to ParseVariableBool() or in the way it is called. Should
> I wait for the dust to settle over there?

I think ParseVariableBool is only likely to change to reject a NULL value
rather than silently interpreting it as FALSE, which is the way it is in
HEAD right now.  That behavior is a leftover hack, really, and moving the
treatment of unset values upstream seems a lot cleaner.  See my draft
patch at
https://www.postgresql.org/message-id/30629.1485881533@sss.pgh.pa.us

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 08:27  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 08:27 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

>
>
> I think ParseVariableBool is only likely to change to reject a NULL value
> rather than silently interpreting it as FALSE, which is the way it is in
> HEAD right now.  That behavior is a leftover hack, really, and moving the
> treatment of unset values upstream seems a lot cleaner.  See my draft
> patch at
> https://www.postgresql.org/message-id/30629.1485881533@sss.pgh.pa.us
>
>                         regards, tom lane
>


Updated patch:
- rebased on post-psql hooks master
- took nearly every suggestion for change to documentation
- \if ERROR will throw the entire \if..\endif into IGNORE mode
- state is now pushed on all \ifs
- reinstated leveraging of ParseVariableBool
- history is now kept in interactive mode regardless of \if-truth
- reworked coding example to cause less agita
- removed MainLoop "are endifs balanced" variable in favor of in-place
check which respects ON_ERROR_STOP.
- make changes to psql/Makefile to enable TAP tests and created t/ directory
- wrote an intentionally failing TAP test to see if "make check" executes
it. it does not. need help.

I'm hoping my failure in that last bit is easy to spot/fix, so I can move
forward with testing unbalanced branching, etc.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 4e51e90..aad62ac 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..a916671 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,29 @@ 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;
+}
+
+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 +231,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd) )
+	{
+		/* 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 +1038,160 @@ 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);
+	}
+
+	/*
+	 * \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_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);
+	}
+
+	/*
+	 * \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_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);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index dc25b4b..9c609b4 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -296,10 +295,15 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				/* execute query if branch is active */
+				if (pset.active_branch)
+					success = SendQuery(query_buf->data);
+				else
+					success = true;
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,7 +362,11 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					/* ignore queries on inactive branches */
+					if (pset.active_branch)
+						success = SendQuery(query_buf->data);
+					else
+						success = true;
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +375,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 +438,11 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		/* execute query if branch is active*/
+		if (pset.active_branch)
+			success = SendQuery(query_buf->data);
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +463,14 @@ 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_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/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 4c7c3b1..15d5599 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 0574b5b..df25864 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..e8cc897
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $stdout;
+my $stderr;
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $retcode = $node->psql('postgres', '\if invalid_expression',
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+is($retcode,'0','Invalid \if respects ON_ERROR_STOP');
+
+$node->teardown_node;
+
+
+
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..a09d404 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,88 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_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_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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(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..a24a5ac 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_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 420825a..ade98c6 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2695,6 +2695,84 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\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
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 79624b9..482bab8 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -365,6 +365,80 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\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

Attachments:

  [text/plain] 0001.if_endif.v4.diff (22.7K, ../../CADkLM=fdBs+G1QrUvqu5MxW7jGg-ULZFcM3Fd2kev6fjrp7-0g@mail.gmail.com/3-0001.if_endif.v4.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 4e51e90..aad62ac 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..a916671 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,29 @@ 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;
+}
+
+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 +231,14 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd) )
+	{
+		/* 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 +1038,160 @@ 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);
+	}
+
+	/*
+	 * \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_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);
+	}
+
+	/*
+	 * \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_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);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		if (psqlscan_branch_empty(scan_state))
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+		else
+		{
+			psqlscan_branch_end_state(scan_state);
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index dc25b4b..9c609b4 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,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *	and sending them to the backend.
@@ -296,10 +295,15 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				/* execute query if branch is active */
+				if (pset.active_branch)
+					success = SendQuery(query_buf->data);
+				else
+					success = true;
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
+				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 
 				/* transfer query to previous_buf by pointer-swapping */
 				{
@@ -358,7 +362,11 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					/* ignore queries on inactive branches */
+					if (pset.active_branch)
+						success = SendQuery(query_buf->data);
+					else
+						success = true;
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +375,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 +438,11 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		/* execute query if branch is active*/
+		if (pset.active_branch)
+			success = SendQuery(query_buf->data);
+		else
+			success = true;
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +463,14 @@ 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_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/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int	MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 4c7c3b1..15d5599 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 0574b5b..df25864 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..e8cc897
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $stdout;
+my $stderr;
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $retcode = $node->psql('postgres', '\if invalid_expression',
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+is($retcode,'0','Invalid \if respects ON_ERROR_STOP');
+
+$node->teardown_node;
+
+
+
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..a09d404 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,88 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_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_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_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(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..a24a5ac 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_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_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 420825a..ade98c6 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2695,6 +2695,84 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\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
+\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
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+\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
+\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
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+\endif
+encountered un-matched \endif
+\else
+encountered un-matched \else
+\elif
+encountered un-matched \elif
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+\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 79624b9..482bab8 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -365,6 +365,80 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\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
+
+\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
+
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+\endif
+
+\else
+
+\elif
+
+\if true
+\else
+\else
+\endif
+
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 09:48  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 09:48 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers


Hello Corey,

Some comments about v4:

Patch applies. "git apply" complained about a space or line somewhere, not 
sure why. make check ok.

> - rebased on post-psql hooks master

Good.

> - took nearly every suggestion for change to documentation

Indeed. Looks ok to me.

> - \if ERROR will throw the entire \if..\endif into IGNORE mode

Ok. I think that it is the better behavior, but other people opinion may 
differ. Opinions are welcome.

> - state is now pushed on all \ifs

Ok.

> - reinstated leveraging of ParseVariableBool

Ok.

> - history is now kept in interactive mode regardless of \if-truth

Ok.

> - reworked coding example to cause less agita

Yep.

> - removed MainLoop "are endifs balanced" variable in favor of in-place
> check which respects ON_ERROR_STOP.

Ok.

> - make changes to psql/Makefile to enable TAP tests and created t/ directory
> - wrote an intentionally failing TAP test to see if "make check" executes
> it. it does not. need help.

A failing test expects code 3, not 0 as written in "t/001_if.pl". With

   is($retcode,'3','Invalid \if respects ON_ERROR_STOP');

instead, the tests succeeds because psql returned 3.

More check should be done about stdout to check that it failed for the 
expected reasons though. And maybe more tests could be added to trigger 
all possible state transition errors (eg else after else, elif after else, 
endif without if, if without endif, whatever...).

> I'm hoping my failure in that last bit is easy to spot/fix, so I can move
> forward with testing unbalanced branching, etc.

Other comments and suggestions:

Variable "slashCmdStatus" is set twice in 3 lines in "mainloop.c"?

There is a spurious empty line added at the very end of "mainloop.h":

   +
    #endif   /* MAINLOOP_H */


I would suggest to add a short one line comment before each test to 
explain what is being tested, like "-- test \elif execution", "-- test 
\else execution"...

Debatable suggestion about "psql_branch_empty": the function name somehow 
suggests an "empty branch", where it is really testing that the stack is 
empty. Maybe the function could be removed and "psql_branch_get_state(...) 
== IF_STATE_NONE" used instead. Not sure.

"psql_branch_end_state": it is a pop, it could be named "psql_branch_pop" 
which would be symmetrical to "psql_branch_push"? Or maybe push should be 
named "begin_state" or "new_state"...

C style details: I would avoid non mandatory parentheses, eg:

   +       return ((strcmp(cmd, "if") == 0 || \
   +                       strcmp(cmd, "elif") == 0 || \
   +                       strcmp(cmd, "else") == 0 || \
   +                       strcmp(cmd, "endif") == 0));

I would remove the external double parentheses (( ... )). Also I'm not 
sure why there are end-of-line backslashes on the first instance, maybe a 
macro turned into a function?

   +       return ((s == IFSTATE_NONE) ||
   +                       (s == IFSTATE_TRUE) ||
   +                       (s == IFSTATE_ELSE_TRUE));

I would remove all parenthenses.

  +       return (state->branch_stack == NULL);

Idem.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 11:31  Erik Rijkers <er@xs4all.nl>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Erik Rijkers @ 2017-02-01 11:31 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2017-02-01 09:27, Corey Huinker wrote:
>  0001.if_endif.v4.diff

A few thoughts after a quick try:

I dislike the ease with which one gets stuck inside an \if block, in 
interactive mode.

(for instance, in my very first session, I tried   '\? \if'  to see if 
there is more info in that help-screen, but it only displays the normal 
help screen.  But after that one cannot exit with  \q  anymore, and 
there is no feedback of any kind (prompt?) in which black hole one has 
ended up.  Only a \endif  provides rescue.)

Therefore making it possible to break out of \if-mode with Ctrl-C would 
be an improvement, I think.
I would even prefer it when  \q would exit psql always, even from within 
\if-mode.

Also, shouldn't  the prompt change inside an \if block?

thanks,

Erik Rijkers



-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:03  Corey Huinker <corey.huinker@gmail.com>
  parent: Erik Rijkers <er@xs4all.nl>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 13:03 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 1, 2017 at 6:31 AM, Erik Rijkers <er@xs4all.nl> wrote:

> On 2017-02-01 09:27, Corey Huinker wrote:
>
>>  0001.if_endif.v4.diff
>>
>
> A few thoughts after a quick try:
>
> I dislike the ease with which one gets stuck inside an \if block, in
> interactive mode.
>
> (for instance, in my very first session, I tried   '\? \if'  to see if
> there is more info in that help-screen, but it only displays the normal
> help screen.  But after that one cannot exit with  \q  anymore, and there
> is no feedback of any kind (prompt?) in which black hole one has ended up.
> Only a \endif  provides rescue.)
>

Good find. I'll have to bulk up the help text.
This raises a question: in interactive mode, should we give some feedback
as to the result of an \if or \elif test? (see below)


>
> Therefore making it possible to break out of \if-mode with Ctrl-C would be
> an improvement, I think.
> I would even prefer it when  \q would exit psql always, even from within
> \if-mode.
>

This whole thing got started with a \quit_if <expr> command, and it was
pointed out that

\if :condition
   \q
\endif

SELECT ... FROM ...


would be preferable. So I don't think we can do that. At least not in
non-interactive mode.

As for CTRL-C, I've never looked into what psql does with CTRL-C, so I
don't know if it's possible, let alone desirable.


>
> Also, shouldn't  the prompt change inside an \if block?
>

That's a good question. I could see us finding ways to print the t/f of
whether a branch is active or not, but I'd like to hear from more people
before diving into something like that.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:18  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 3 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 13:18 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

>
> - wrote an intentionally failing TAP test to see if "make check" executes
>> it. it does not. need help.
>>
>
> A failing test expects code 3, not 0 as written in "t/001_if.pl". With
>
>   is($retcode,'3','Invalid \if respects ON_ERROR_STOP');
>
> instead, the tests succeeds because psql returned 3.
>

Right. What I meant was, no matter how I changed that test, I could not get
it to fail, which made me think it was not executing at all. What do I need
to do to get TAP tests running? I must be missing something.


> More check should be done about stdout to check that it failed for the
> expected reasons though. And maybe more tests could be added to trigger all
> possible state transition errors (eg else after else, elif after else,
> endif without if, if without endif, whatever...).


Agreed. But I couldn't build further on the test without being sure it was
being run.


>
> Other comments and suggestions:
>
> Variable "slashCmdStatus" is set twice in 3 lines in "mainloop.c"?
>

I think that's a merge error from rebasing. Will fix.


>
> There is a spurious empty line added at the very end of "mainloop.h":
>
>   +
>    #endif   /* MAINLOOP_H */
>

Not in my diff, but that's been coming and going in your diff reviews.


>
>
> I would suggest to add a short one line comment before each test to
> explain what is being tested, like "-- test \elif execution", "-- test
> \else execution"...
>

Where are you suggesting this?


>
> Debatable suggestion about "psql_branch_empty": the function name somehow
> suggests an "empty branch", where it is really testing that the stack is
> empty. Maybe the function could be removed and "psql_branch_get_state(...)
> == IF_STATE_NONE" used instead. Not sure.
>

The name isn't great. Maybe psql_branch_stack_empty()?

"psql_branch_end_state": it is a pop, it could be named "psql_branch_pop"
> which would be symmetrical to "psql_branch_push"? Or maybe push should be
> named "begin_state" or "new_state"...
>

Yeah, we either need to go fully with telling the programmer that it's a
stack (push/pop/empty) or (begin_branch/end_branch/not_branching). I'm
inclined to go full-stack, as it were.



>
> C style details: I would avoid non mandatory parentheses, eg:
>
>   +       return ((strcmp(cmd, "if") == 0 || \
>   +                       strcmp(cmd, "elif") == 0 || \
>   +                       strcmp(cmd, "else") == 0 || \
>   +                       strcmp(cmd, "endif") == 0));
>
> I would remove the external double parentheses (( ... )). Also I'm not
> sure why there are end-of-line backslashes on the first instance, maybe a
> macro turned into a function?
>

I copied that from somewhere, and I don't remember where. I think the test
was originally nested much further before being moved to its own function.
Can fix.


>
>   +       return ((s == IFSTATE_NONE) ||
>   +                       (s == IFSTATE_TRUE) ||
>   +                       (s == IFSTATE_ELSE_TRUE));
>
> I would remove all parenthenses.
>

+1


>
>  +       return (state->branch_stack == NULL);
>
> Idem.


+1

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:25  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 13:25 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Good find. I'll have to bulk up the help text.

Yes.

> This raises a question: in interactive mode, should we give some feedback
> as to the result of an \if or \elif test? (see below)

Obviously \if makes more sense for scripting.

However I would say yes, it should provide some feedback... This means 
probably adding a new prompt substitution "%<something>". In the worst 
case, the prompt should reflect the current stack, or at least the top of 
the task...

Maybe use "%?" which could be substituted by:

     empty stack   -> ""
     ignore state  -> "." or "(i)"
     *_true state  -> "t" or "(t)"
     *_false state -> "f" or "(f)"

     calvin=>   \if true
     calvin=(t)>  \echo "running..."
       running...
     calvin=(t)>  \else
     calvin=(f)>   whatever
     calvin=(f)>  \endif
     calvin=>


>> Therefore making it possible to break out of \if-mode with Ctrl-C would be
>> an improvement, I think.
>> I would even prefer it when  \q would exit psql always, even from within
>> \if-mode.
> So I don't think we can do that. At least not in non-interactive mode.

Yep.

> As for CTRL-C, I've never looked into what psql does with CTRL-C, so I
> don't know if it's possible, let alone desirable.

I think that ctrl-c should abandon current command, which is what the user 
expect when things go wrong. I would suggest to pop the stack on ctrl-C on 
an empty input, eg:

   calvin=> \if true
   calvin=(t)> SELECT
   calvin-(t)>   <ctrl-C>
   calvin=(t)> <ctrl-C>
   calvin=>

>> Also, shouldn't  the prompt change inside an \if block?
>
> That's a good question. I could see us finding ways to print the t/f of
> whether a branch is active or not, but I'd like to hear from more people
> before diving into something like that.

See above.

Adding a state indicator is probably ok, the key question is whether the 
default prompt is changed.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:33  Fabien COELHO <fabien.coelho@mines-paristech.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 13:33 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers


Hello,

> What do I need to do to get TAP tests running?

I misunderstood. You need to configure with "--enable-tap-tests".

>> There is a spurious empty line added at the very end of "mainloop.h":
>>
>>   +
>>    #endif   /* MAINLOOP_H */
>
> Not in my diff, but that's been coming and going in your diff reviews.

Strange. Maybe this is linked to the warning displayed with "git apply" 
when I apply the diff.

>> I would suggest to add a short one line comment before each test to
>> explain what is being tested, like "-- test \elif execution", "-- test
>> \else execution"...
>
> Where are you suggesting this?

In "regress/sql/psql.sql", in front of each group which starts a test.

>> Debatable suggestion about "psql_branch_empty":
>
> The name isn't great. Maybe psql_branch_stack_empty()?

Yep, maybe, or "empty_stack" or "stack_is_empty" or IDK...

> "psql_branch_end_state": it is a pop, it could be named "psql_branch_pop"
>
> Yeah, we either need to go fully with telling the programmer that it's a
> stack (push/pop/empty) or (begin_branch/end_branch/not_branching). I'm
> inclined to go full-stack, as it were.

Anything consistent is ok. I'm fine with calling a stack a stack:-)

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:48  Erik Rijkers <er@xs4all.nl>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 1 reply; 232+ messages in thread

From: Erik Rijkers @ 2017-02-01 13:48 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2017-02-01 14:18, Corey Huinker wrote:
> to do to get TAP tests running? I must be missing something.

Guesswork on my part:

Add  --enable-tap-tests option  on ./configure

Run   make check-world   (as opposed to  just  make check )


Erik Rijkers


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 13:53  Corey Huinker <corey.huinker@gmail.com>
  parent: Erik Rijkers <er@xs4all.nl>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 13:53 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> Add  --enable-tap-tests option  on ./configure
>

This much I had already done.


>
> Run   make check-world   (as opposed to  just  make check )


I'll give that a shot.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 14:00  Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 14:00 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


>> Run   make check-world   (as opposed to  just  make check )
>
> I'll give that a shot.

You can also run "make check" directly in the "src/bin/psql" directory.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 14:28  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  2 siblings, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-01 14:28 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers

Corey Huinker <corey.huinker@gmail.com> writes:
> Right. What I meant was, no matter how I changed that test, I could not get
> it to fail, which made me think it was not executing at all. What do I need
> to do to get TAP tests running? I must be missing something.

configure --enable-tap-tests, perhaps?

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 15:33  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <fabien.coelho@mines-paristech.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 15:33 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers


<sorry, resent, wrong from again *$%@#&!?>

Hello Corey,

>> There is a spurious empty line added at the very end of "mainloop.h":
>>
>>   +
>>    #endif   /* MAINLOOP_H */
> 
> Not in my diff, but that's been coming and going in your diff reviews.

Strange. Maybe this is linked to the warning displayed with "git apply" when 
I apply the diff.

>> I would suggest to add a short one line comment before each test to
>> explain what is being tested, like "-- test \elif execution", "-- test
>> \else execution"...
> 
> Where are you suggesting this?

In "regress/sql/psql.sql", in front of each group which starts a test.

>> Debatable suggestion about "psql_branch_empty":
> 
> The name isn't great. Maybe psql_branch_stack_empty()?

  Yep, maybe, or "empty_stack" or "stack_is_empty" or IDK...

> "psql_branch_end_state": it is a pop, it could be named "psql_branch_pop"
> 
> Yeah, we either need to go fully with telling the programmer that it's a
> stack (push/pop/empty) or (begin_branch/end_branch/not_branching). I'm
> inclined to go full-stack, as it were.

Anything consistent is ok. I'm fine with calling a stack a stack:-)

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 17:58  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 17:58 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> However I would say yes, it should provide some feedback... This means
> probably adding a new prompt substitution "%<something>". In the worst
> case, the prompt should reflect the current stack, or at least the top of
> the task...
>

We could just issue interactive-only warnings when:
- A user types a branching condition command which sets the branch inactive
- A user types a command or query when the current branch is inactive.

The warnings could be specific about state, something like:

psql session is now in an inactive \if branch. No queries will be executed
and only branching commands (\if, \elif, \else, \endif) will be evaluated.

psql session is now in an inactive \elif branch. No queries will be
executed and only branching commands (\if, \elif, \else, \endif) will be
evaluated.

psql session is now in an inactive \else branch. No queries will be
executed and only branching commands (\if, \endif) will be evaluated.



This could of course be done in addition to prompt changes, and is
orthogonal to the CTRL-c  option. I'd like more input before moving forward
on either of those, as they have a good chance to clobber other expected
behaviors.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 18:09  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 18:09 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 1, 2017 at 8:53 AM, Corey Huinker <corey.huinker@gmail.com>
wrote:

>
>> Run   make check-world   (as opposed to  just  make check )
>
>
> I'll give that a shot.
>

That was it. Tests don't run if you don't invoke them. Thanks.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 18:12  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-01 18:12 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> You can also run "make check" directly in the "src/bin/psql" directory.


Previously, that didn't do anything, but now that I've created a TAP test
it does. It doesn't, however, run the psql regress tests. But at least I
can use the two commands in combination and not have to run *all* TAP tests
outside of psql.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 20:03  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-01 20:03 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello,

> We could just issue interactive-only warnings when:
> - A user types a branching condition command which sets the branch inactive
> - A user types a command or query when the current branch is inactive.
>
> The warnings could be specific about state, something like:
>
> psql session is now in an inactive \if branch. No queries will be executed
> and only branching commands (\if, \elif, \else, \endif) will be evaluated.
> [...]

My 0.02€: it looks too verbose, should stay on one short line. Maybe:

   (in|)active (\if|\elif|\else), (execut|ignor)ing commands

Although there is some redundancy...

    calvin=>    \if true
      active \if: executing commands
    calvin=(t)> \echo ok
      ok
    calvin=(t)> \else
      inactive \else: skipping commands
    calvin=(f)> ...

Maybe it could be controlled, say based on VERBOSITY setting (which really 
controls verbosity of error reports) or some other.

I'm unsure whether it is a good idea, I like terse interfaces, but this is 
just an opinion.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-01 21:58  Jim Nasby <Jim.Nasby@BlueTreble.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Jim Nasby @ 2017-02-01 21:58 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2/1/17 12:03 PM, Fabien COELHO wrote:
> I'm unsure whether it is a good idea, I like terse interfaces, but this
> is just an opinion.

I think the issue here is that the original case for this is a user 
accidentally getting into an \if and then having no clue what's going 
on. That's similar to what happens when you miss a quote or a semicolon. 
We handle those cases with %R, and I think %R needs to support if as well.

Perhaps there's value to providing more info (active branch, etc), but 
ISTM trying to do that will just confuse the original (%R) case.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-02 22:39  Corey Huinker <corey.huinker@gmail.com>
  parent: Jim Nasby <Jim.Nasby@BlueTreble.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-02 22:39 UTC (permalink / raw)
  To: Jim Nasby <Jim.Nasby@bluetreble.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 1, 2017 at 4:58 PM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

> I think the issue here is that the original case for this is a user
> accidentally getting into an \if and then having no clue what's going on.
> That's similar to what happens when you miss a quote or a semicolon. We
> handle those cases with %R, and I think %R needs to support if as well.
>
> Perhaps there's value to providing more info (active branch, etc), but
> ISTM trying to do that will just confuse the original (%R) case.
>

Jim,

After spending a few minutes to familiarize myself with %R, I'm in
agreement with your second statement (adding if-else to %R will just
confuse %R). However, your first statement seems to indicate the opposite.
Can you elaborate?

All,
As it is, I've added interactive mode psql_error notifications about the
resulting branching state of any branching commands, and any attempt to
send non-branching commands or queries while in an inactive branch will
generate a psql_error saying that the command was ignored. Waiting til I
get what should or shouldn't be done about prompts before issuing the next
patch revision.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-02 23:29  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-02 23:29 UTC (permalink / raw)
  To: Jim Nasby <Jim.Nasby@bluetreble.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> All,
> As it is, I've added interactive mode psql_error notifications about the
> resulting branching state of any branching commands, and any attempt to
> send non-branching commands or queries while in an inactive branch will
> generate a psql_error saying that the command was ignored. Waiting til I
> get what should or shouldn't be done about prompts before issuing the next
> patch revision.
>

So far, interactive branching information will look like this (it prints on
every branching command and on every ignored command):

# \if true
active \if, executing commands
# select 1;
 ?column?
----------
        1
(1 row)

Time: 0.282 ms
# \else
inactive \else, ignoring commands
# select 1;
inside inactive branch, query ignored.
# select
... # 1;
inside inactive branch, query ignored.
# \endif
active \endif, executing commands
# \if false
inactive \if, ignoring commands
# \i file_name
inside inactive branch, command ignored.
# \elif false
inactive \elif, ignoring commands
# \else
active \else, executing commands
# \endif
active \endif, executing commands


Comments welcome.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 01:32  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-02-03 01:32 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Jim Nasby <Jim.Nasby@bluetreble.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
>> As it is, I've added interactive mode psql_error notifications about the
>> resulting branching state of any branching commands, and any attempt to
>> send non-branching commands or queries while in an inactive branch will
>> generate a psql_error saying that the command was ignored. Waiting til I
>> get what should or shouldn't be done about prompts before issuing the next
>> patch revision.

On reflection, it seems fairly improbable to me that people would use
\if and friends interactively.  They're certainly useful for scripting,
but would you really type commands that you know are going to be ignored?

Therefore, I don't think we should stress out about fitting branching
activity into the prompts.  That's just not the use-case.  (Note: we
might well have to reconsider that if we get looping, but for now it's
not a problem.)  Moreover, if someone is confused because they don't
realize they're inside a failed \if, it's unlikely that a subtle change in
the prompt would help them.  So your more in-the-face approach of printing
messages seems good to me.

> So far, interactive branching information will look like this (it prints on
> every branching command and on every ignored command):

This seems more or less reasonable, although:

> # \endif
> active \endif, executing commands

looks a bit weird.  Maybe instead say "exited \if, executing commands"?

BTW, what is your policy about nesting these things in include files?
My immediate inclination is that if we hit EOF with open \if state,
we should drop it and revert to the state in the surrounding file.
Otherwise things will be way too confusing.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 02:06  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 02:06 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jim Nasby <Jim.Nasby@bluetreble.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> On reflection, it seems fairly improbable to me that people would use
> \if and friends interactively.  They're certainly useful for scripting,
> but would you really type commands that you know are going to be ignored?
>

I'm thinking the one use-case is where a person is debugging a
non-interactive script, cuts and pastes it into an interactive script, and
then scrolls through command history to fix the bits that didn't work. So,
no, you wouldn't *type* them per se, but you'd want the session as if you
had. The if-then barking might really be useful in that context.


>
> Therefore, I don't think we should stress out about fitting branching
> activity into the prompts.  That's just not the use-case.  (Note: we
> might well have to reconsider that if we get looping, but for now it's
> not a problem.)  Moreover, if someone is confused because they don't
> realize they're inside a failed \if, it's unlikely that a subtle change in
> the prompt would help them.  So your more in-the-face approach of printing
> messages seems good to me.
>

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), and
it could print the if-state stack, maybe something like:

\if true
  \if true
     \if false
        \if true

With a prompt1 of '%T> ' Would then resolve to

ttfi>

for true, true, false, ignored.

This is just idle musing, I'm perfectly happy to leave it out entirely.


> This seems more or less reasonable, although:
>
> > # \endif
> > active \endif, executing commands
>
> looks a bit weird.  Maybe instead say "exited \if, executing commands"?
>

+1


>
> BTW, what is your policy about nesting these things in include files?
> My immediate inclination is that if we hit EOF with open \if state,
> we should drop it and revert to the state in the surrounding file.
> Otherwise things will be way too confusing.
>

That's how it works now if you have ON_ERROR_STOP off, plus an error
message telling you about the imbalance. If you have ON_ERROR_STOP on, it's
fatal.

All \if-\endif pairs must be balanced within a file (well, within a
MainLoop, but to the user it looks like a file). Every new file opened via
\i or \ir starts a new if-stack. Because commands in an inactive branch are
never executed, we don't have to worry about the state of the parent stack
when we do a \i, because we know it's trues all the way down.

We chose this not so much because if-endif needed it (we could have thrown
it into the pset struct just as easily), but because of the issues that
might come up with a \while loop: needing to remember previous GOTO points
in a file (if the input even *is* a file...) is going to be hard enough,
remembering them across files would be harder, and further complicated by
the possibility that a file \included on one iteration might not be
included on the next (or vice versa)...and like you said, way too confusing.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 05:57  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-03 05:57 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


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.

> With a prompt1 of '%T> ' Would then resolve to "ttfi" [...]
> This is just idle musing, I'm perfectly happy to leave it out entirely.

I like it. I would prefer to have it available, but my advice is to follow 
committer' opinions. At the minimum, there must be some trace, either 
"barking" or "prompting".

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 07:16  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 07:16 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 12:57 AM, Fabien COELHO <coelho@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

Attachments:

  [text/plain] 0001.if_endif.v5.diff (25.7K, ../../CADkLM=e6u013=KqqGwWEaGHWO0hPKnmLd=UYJf5OKP45fDJp=g@mail.gmail.com/3-0001.if_endif.v5.diff)
  download | inline diff:
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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 11:00  Erik Rijkers <er@xs4all.nl>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Erik Rijkers @ 2017-02-03 11:00 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2017-02-03 08:16, Corey Huinker wrote:

> 0001.if_endif.v5.diff

1. Well, with this amount of interactive output  it is impossible to get 
stuck without knowing :)
This is good. Still, it would be an improvement to be able to break out 
of an inactive \if-branch
with Ctrl-C.  (I noticed that inside an active branch it is already 
possible )
'\endif' is too long to type, /and/ you have to know it.

2. Inside an \if block  \q should be given precedence and cause a direct 
exit of psql (or at the
very least exit the if block(s)), as in regular SQL statements
(compare: 'select * from t  \q'  which will immediately exit psql -- 
this is good. )

3. I think the 'barking' is OK because interactive use is certainly not 
the first use-case.
But nonetheless it could be made a bit more terse without losing its 
function.
The interactive behavior is now:
# \if 1
entered if: active, executing commands
# \elif 0
entered elif: inactive, ignoring commands
# \else
entered else: inactive, ignoring commands
# \endif
exited if: active, executing commands

It really is a bit too wordy, IMHO; I would say, drop all 'entered', 
'active',  and 'inactive' words.
That leaves it plenty clear what's going on.
That would make those lines:
if: executing commands
elif: ignoring commands
else: ignoring commands
exited if
    (or alternatively, just  mention 'if: active' or 'elif: inactive', 
etc., which has the advantage of being shorter)

5. A real bug, I think:
#\if asdasd
unrecognized value "asdasd" for "\if <expr>": boolean expected
# \q;
inside inactive branch, command ignored.
#

That 'unrecognized value' message is fair enough but it is 
counterintuitive that after an erroneous opening \if-expression, the 
if-modus should be entered into. ( and now I have to type \endif 
again... )

6. About the help screen:
There should be an empty line above 'Conditionals' to visually divide it 
from other help items.

The indenting of the new block is incorrect: the lines that start with
      fprintf(output, _("  \\
are indented to the correct level; the other lines are indented 1 place 
too much.

The help text has a few typos (some multiple times):
queires -> queries
exectue -> execute
subsequennt -> subsequent

Thanks,

Erik Rijkers


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 12:24  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Erik Rijkers <er@xs4all.nl>
  0 siblings, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-03 12:24 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Erik,

> Still, it would be an improvement to be able to break out of an inactive 
> \if-branch with Ctrl-C.

Yes.

> '\endif' is too long to type, /and/ you have to know it.

Yep. \if is shorter but has been rejected. Ctrl-C should be the way out.

> 2. Inside an \if block \q should be given precedence and cause a direct 
> exit of psql (or at the very least exit the if block(s)), as in regular 
> SQL statements (compare: 'select * from t \q' which will immediately 
> exit psql -- this is good. )

One use case if to be able to write "\if ... \q \endif" in scripts. If \q 
is always executed, then the use case is blown. I cannot think of any 
language that would execute anything in a false branch. So Ctrl-C or 
Ctrl-D is the way out, and \if control must really have precedence over 
its contents.

> 3. I think the 'barking' is OK because interactive use is certainly not the 
> first use-case.
> But nonetheless it could be made a bit more terse without losing its 
> function.

> [...] It really is a bit too wordy, [...]

Maybe.

> (or alternatively, just  mention 'if: active' or 'elif: inactive', etc., 
> which has the advantage of being shorter)

This last version is too terse I think. The point is that the user 
understands whether their commands are going to be executed or ignored, 
and "active/inactive" is not very clear.

> 5. A real bug, I think:
> #\if asdasd
> unrecognized value "asdasd" for "\if <expr>": boolean expected
> # \q;
> inside inactive branch, command ignored.
> #
>
> That 'unrecognized value' message is fair enough but it is counterintuitive 
> that after an erroneous opening \if-expression, the if-modus should be 
> entered into. ( and now I have to type \endif again... )

Hmmm.

It should tell that it is in an unclosed if and that it is 
currently ignoring commands, so the "barking" is missing.

Otherwise that is really the currently desired behavior for scripting use:

   \if syntax-error...
     DROP USER foo;
   \elif ...
     DROP DATABASE foo;
   \else
     ...
   \endif

If the "\if" is simply ignored, then it is going to execute everything 
that appears after that, whereas the initial condition failed to be 
checked, and it will proceed to ignore all further \elif and \else in 
passing.

Also, I do not think that implementing a different behavior for 
interactive is a good idea, because then typing directly and reading a 
file would result in different behaviors, which would not help debugging.

So, as Tom suggested (I think), the feature is not designed for 
interactive use, so it does not need to be optimized for that purpose,
although it should be sane enough.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 13:24  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-03 13:24 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


>> 2. Inside an \if block \q should be given precedence and cause a direct 
>> exit of psql (or at the very least exit the if block(s)), as in regular SQL 
>> statements (compare: 'select * from t \q' which will immediately exit psql 
>> -- this is good. )
>
> One use case if to be able to write "\if ... \q \endif" in scripts. If \q is 
> always executed, then the use case is blown. I cannot think of any language 
> that would execute anything in a false branch. So Ctrl-C or Ctrl-D is the way 
> out, and \if control must really have precedence over its contents.

After giving it some more thoughts, a possible solution could be to have a 
"\exit [status]" which could be ignored (there has been some talk about 
that one), and have \q which is not, but I would find it weird and error 
prone for the user. As already said, \if use-case is not about interactive 
psql.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 16:08  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 16:08 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 7:24 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Erik,
>
> Still, it would be an improvement to be able to break out of an inactive
>> \if-branch with Ctrl-C.
>>
>
> Yes.
>
> '\endif' is too long to type, /and/ you have to know it.
>>
>
> Yep. \if is shorter but has been rejected. Ctrl-C should be the way out.


I could bulk up the error message on if/elif like such:

if: true, executing commands.
if: false, ignoring commands until next \else, \elif, or \endif.
if: error, ignoring all commands until next \endif
else: true, executing commands
else: false, ignoring commands until next \endif
else: error, ignoring commands until next \endif
endif: now executing commands
endif: ignoring commands until next [\else, [\elif [, \endif]]


Basically, I'd tailor the message to as closely reflect what is possible
for the user at this moment.


Can anyone think of a reason why Ctrl-C would be a bad idea? If not I'll
start looking into it, as I'm not presently aware of what it is used for.


2. Inside an \if block \q should be given precedence and cause a direct
>> exit of psql (or at the very least exit the if block(s)), as in regular SQL
>> statements (compare: 'select * from t \q' which will immediately exit psql
>> -- this is good. )
>>
>
> One use case if to be able to write "\if ... \q \endif" in scripts. If \q
> is always executed, then the use case is blown. I cannot think of any
> language that would execute anything in a false branch. So Ctrl-C or Ctrl-D
> is the way out, and \if control must really have precedence over its
> contents.
>
> 3. I think the 'barking' is OK because interactive use is certainly not
>> the first use-case.
>> But nonetheless it could be made a bit more terse without losing its
>> function.
>>
>
> [...] It really is a bit too wordy, [...]
>>
>
> Maybe.
>
> (or alternatively, just  mention 'if: active' or 'elif: inactive', etc.,
>> which has the advantage of being shorter)
>>
>
> This last version is too terse I think. The point is that the user
> understands whether their commands are going to be executed or ignored, and
> "active/inactive" is not very clear.
>
> 5. A real bug, I think:
>> #\if asdasd
>> unrecognized value "asdasd" for "\if <expr>": boolean expected
>> # \q;
>> inside inactive branch, command ignored.
>> #
>>
>> That 'unrecognized value' message is fair enough but it is
>> counterintuitive that after an erroneous opening \if-expression, the
>> if-modus should be entered into. ( and now I have to type \endif again... )
>>
>
> Hmmm.
>
> It should tell that it is in an unclosed if and that it is currently
> ignoring commands, so the "barking" is missing.
>

It does need a bespoke bark.

Also, I do not think that implementing a different behavior for interactive
> is a good idea, because then typing directly and reading a file would
> result in different behaviors, which would not help debugging.
>

+1


>
> So, as Tom suggested (I think), the feature is not designed for
> interactive use, so it does not need to be optimized for that purpose,
> although it should be sane enough.


+1

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 16:38  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-03 16:38 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> I could bulk up the error message on if/elif like such: [...]

Looks ok to me.

> Can anyone think of a reason why Ctrl-C would be a bad idea? If not I'll
> start looking into it, as I'm not presently aware of what it is used for.

Not me.

Wikipedia, which holds all the knowledge in the universe, says: "In many 
command-line interface environments, control-C is used to abort the 
current task and regain user control."

I agree that it should do that.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 16:49  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 16:49 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> Can anyone think of a reason why Ctrl-C would be a bad idea? If not I'll
>> start looking into it, as I'm not presently aware of what it is used for.
>>
>
> Not me.
>
> Wikipedia, which holds all the knowledge in the universe, says: "In many
> command-line interface environments, control-C is used to abort the current
> task and regain user control."
>

Well played (again). That one ranks up there with "and don't call me
Shirley." I meant in the specific psql-context, does it do anything other
than (attempt to) terminate sent-but-not-received SQL queries?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 17:10  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-03 17:10 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> Well played (again). That one ranks up there with "and don't call me
> Shirley." I meant in the specific psql-context, does it do anything other
> than (attempt to) terminate sent-but-not-received SQL queries?

It also flushes the input buffer.  I think it is probably reasonable to
let it cancel interactive \if state as well.  A useful thought-experiment
is to ask what behavior you'd want if we had metacommand loops ... and
I think the answer there is pretty obvious: you'd want control-C to kill
a loop.

I'm less sure about what it ought to do when control is somewhere in
a script file.  I *think* we have things set up to kill execution of
script files altogether, in which case we have the answer a fortiori.
If not, there's room for discussion.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 17:20  Daniel Verite <daniel@manitou-mail.org>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-02-03 17:20 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

	Corey Huinker wrote:

> I meant in the specific psql-context, does it do anything other
> than (attempt to) terminate sent-but-not-received SQL queries?

It cancels the current edit in the query buffer, including the
case when it spans multiple lines.
If we add the functionality that Ctrl+C also exits from branches,
we could do it like the shell does Ctrl+D for logout, that is it
logs out only if the input buffer is empty, otherwise it does
the other functionality bound to this key (normally Delete).
So if you're in the middle of an edit, the first Ctrl+C will
cancel the edit and a second one will go back from the /if

Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 20:03  Corey Huinker <corey.huinker@gmail.com>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 20:03 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 12:20 PM, Daniel Verite <daniel@manitou-mail.org>
wrote:

> If we add the functionality that Ctrl+C also exits from branches,
> we could do it like the shell does Ctrl+D for logout, that is it
> logs out only if the input buffer is empty, otherwise it does
> the other functionality bound to this key (normally Delete).
> So if you're in the middle of an edit, the first Ctrl+C will
> cancel the edit and a second one will go back from the /if
>

That does seem to be the consensus desired behavior. I'm just not sure
where to handle that. The var "cancel_pressed" shows up in a lot of places.
Advice?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 20:49  Robert Haas <robertmhaas@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Robert Haas @ 2017-02-03 20:49 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 11:08 AM, Corey Huinker <corey.huinker@gmail.com> wrote:
> I could bulk up the error message on if/elif like such:
>
> if: true, executing commands.
> if: false, ignoring commands until next \else, \elif, or \endif.
> if: error, ignoring all commands until next \endif
> else: true, executing commands
> else: false, ignoring commands until next \endif
> else: error, ignoring commands until next \endif
> endif: now executing commands
> endif: ignoring commands until next [\else, [\elif [, \endif]]
>
> Basically, I'd tailor the message to as closely reflect what is possible for
> the user at this moment.

I think that this is kinda hairy.  When I see "endif: now executing
commands", my reaction is "oh crap, which commands are you
executing?".  What you really mean is that future command are expected
to be executed unless things change (for example, due to another \if
in the meantime), but somebody might have a different interpretation
of these messages.

I think that the messages you are proposing for "if" and "else" are
reasonable, but for "endif" I would just say "endif: exiting if" or
something like that.  If the user doesn't know to what state they are
returning, c'est la vie.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 21:24  Corey Huinker <corey.huinker@gmail.com>
  parent: Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-03 21:24 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 3:49 PM, Robert Haas <robertmhaas@gmail.com> wrote:

> On Fri, Feb 3, 2017 at 11:08 AM, Corey Huinker <corey.huinker@gmail.com>
> wrote:
> > I could bulk up the error message on if/elif like such:
> >
> > if: true, executing commands.
> > if: false, ignoring commands until next \else, \elif, or \endif.
> > if: error, ignoring all commands until next \endif
> > else: true, executing commands
> > else: false, ignoring commands until next \endif
> > else: error, ignoring commands until next \endif
> > endif: now executing commands
> > endif: ignoring commands until next [\else, [\elif [, \endif]]
> >
> > Basically, I'd tailor the message to as closely reflect what is possible
> for
> > the user at this moment.
>
> I think that this is kinda hairy.  When I see "endif: now executing
> commands", my reaction is "oh crap, which commands are you
> executing?".  What you really mean is that future command are expected
> to be executed unless things change (for example, due to another \if
> in the meantime), but somebody might have a different interpretation
> of these messages.
>
> I think that the messages you are proposing for "if" and "else" are
> reasonable, but for "endif" I would just say "endif: exiting if" or
> something like that.  If the user doesn't know to what state they are
> returning, c'est la vie.
>

That might be what we end up doing. I'm willing to see how unwieldy it gets
before rolling back to "endif: peace out".

The state logic has stuff to do anyway, so for the moment I've added
psql_error() messages at each endpoint. My current (unsubmitted) work has:

if you were in a true branch and leave it  (i.e yes->yes)

+                                       psql_error("exited \\if to true
parent branch, \n"
+                                                       "continuing
executing commands\n");

if you were in a false branch beneath a true branch and leave it  (no->yes)

+                                       psql_error("exited \\if to true
parent branch, \n"
+                                                       "resuming executing
commands\n");

And if you were in a branch that was a child of a false branch (no->no):

+                               psql_error("exited \\if to false parent
branch, \n"
+                                               "ignoring commands until
next \\endif\n");

And the (yes->no) is an impossibility, so no message there.

I'm not too concerned about what wording we finally go with, and as the
coder I realize I'm too close to know the wording that will be most helpful
to an outsider, so I'm very much trusting others to guide me.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-03 21:37  Robert Haas <robertmhaas@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Robert Haas @ 2017-02-03 21:37 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 4:24 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
> That might be what we end up doing. I'm willing to see how unwieldy it gets
> before rolling back to "endif: peace out".

All I'm saying is, give peace a chance.  :-)

> The state logic has stuff to do anyway, so for the moment I've added
> psql_error() messages at each endpoint. My current (unsubmitted) work has:
>
> if you were in a true branch and leave it  (i.e yes->yes)
>
> +                                       psql_error("exited \\if to true
> parent branch, \n"
> +                                                       "continuing
> executing commands\n");
>
> if you were in a false branch beneath a true branch and leave it  (no->yes)
>
> +                                       psql_error("exited \\if to true
> parent branch, \n"
> +                                                       "resuming executing
> commands\n");
>
> And if you were in a branch that was a child of a false branch (no->no):
>
> +                               psql_error("exited \\if to false parent
> branch, \n"
> +                                               "ignoring commands until
> next \\endif\n");
>
> And the (yes->no) is an impossibility, so no message there.
>
> I'm not too concerned about what wording we finally go with, and as the
> coder I realize I'm too close to know the wording that will be most helpful
> to an outsider, so I'm very much trusting others to guide me.

But by far the most likely case is that you are not under another \if
at all, and none of these messages are really apropos for that case.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 00:42  Jim Nasby <Jim.Nasby@BlueTreble.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Jim Nasby @ 2017-02-04 00:42 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2/2/17 4:39 PM, Corey Huinker wrote:
>
> On Wed, Feb 1, 2017 at 4:58 PM, Jim Nasby <Jim.Nasby@bluetreble.com
> <mailto:Jim.Nasby@bluetreble.com>> wrote:
>
>     I think the issue here is that the original case for this is a user
>     accidentally getting into an \if and then having no clue what's
>     going on. That's similar to what happens when you miss a quote or a
>     semicolon. We handle those cases with %R, and I think %R needs to
>     support if as well.
>
>     Perhaps there's value to providing more info (active branch, etc),
>     but ISTM trying to do that will just confuse the original (%R) case.
>
>
> Jim,
>
> After spending a few minutes to familiarize myself with %R, I'm in
> agreement with your second statement (adding if-else to %R will just
> confuse %R). However, your first statement seems to indicate the
> opposite. Can you elaborate?

My point was that we need a way for users to know if they're stuck in an 
\if block, and right now that's handled with %R (inside transaction, 
parens, etc). My other point is that adding all the extra info to %R 
would be folly.

Since the current consensus is to be very verbose about \if, this is 
obviously a non-issue. Maybe worth adding a 'I' case to %R, but no big 
deal if that doesn't happen.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 01:21  Corey Huinker <corey.huinker@gmail.com>
  parent: Jim Nasby <Jim.Nasby@BlueTreble.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-04 01:21 UTC (permalink / raw)
  To: Jim Nasby <Jim.Nasby@bluetreble.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Feb 3, 2017 at 7:42 PM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

> Since the current consensus is to be very verbose about \if, this is
> obviously a non-issue. Maybe worth adding a 'I' case to %R, but no big deal
> if that doesn't happen.


I think we left the door open to a separate patch for a prompt change.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 07:26  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-04 07:26 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


A few comments about v5.

> New patch.

Patch applies (with patch, I gave up on "git apply").
Make check ok.
Psql tap test ok.

> Highlights:
> - Interactive barking on branching state changes, commands typed while in
> inactive state

I noticed that the "barking" is conditional to "success". ISTM that it 
should always "bark" in interactive mode, whether success or not.

While testing it and seeing the code, I agree that it is too 
verbose/redundant. At least remove "active/inactive, ".

> - Help text. New block in help text called "Conditionals"

Maybe it could be moved to "Input/Output" renamed as "Input/Output 
Control", or maybe the "Conditionals" section could be moved next to it, 
it seems more logical than after large objects.

I think that the descriptions are too long. The interactive user can be 
trusted to know what "if/elif/else/endif" mean, or to refer to the full 
documentation otherwise. The point is just to provide a syntax and 
function reminder, not a substitute for the doc. Thus I would suggest 
shorter one-line messages like:

  \if <expr>    begin a new conditional block
  \elif <expr>  else if in the current conditional block
  \else         else in current conditional block
  \endif        end current conditional block

There should not be a \n at the end, I think, but just between sections.

> - SendQuery calls in mainloop.c are all encapsulated in send_query() to
> ensure the same if-active and if-interactive logic is used

Ok.

> - Exactly one perl TAP test, testing ON_ERROR_STOP. I predict more will be
> needed, but I'm not sure what coverage is desired

More that one:-)

> - I also predict that my TAP test style is pathetic

Hmmm. Perl is perl. Attached an attempt at improving that, which is 
probably debatable, but at least it is easy to add further tests without 
massive copy-pasting.

> - regression tests now have comments to explain purpose

Ok.

Small details about the code:

   +       if (!pset.active_branch && !is_branching_command(cmd) )

Not sure why there is a space before the last closing parenthesis.

-- 
Fabien.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-perl] 001_if.pl (1.1K, ../../alpine.DEB.2.20.1702040739120.20076@lancre/2-001_if.pl)
  download | inline:
use strict;
use warnings;

use Config;
use PostgresNode;
use TestLib;
use Test::More tests => 15;

#
# test invalid \if respects ON_ERROR_STOP
#
my $node = get_new_node('master');
$node->init;
$node->start;

my $tests = [
  [ "\\if invalid_expression\n\\endif\n", '', 'boolean expected', 'syntax error' ],
  # unmatched checks
  [ "\\if true\\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
  [ "\\elif true\\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
  [ "\\else\\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
  [ "\\endif\\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
];

# 3 checks per tests
for my $test (@$tests) {
  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
  my ($stdout, $stderr);
  my $retcode = $node->psql('postgres', $script,
		stdout => \$stdout, stderr => \$stderr,
		on_error_stop => 1);
  is($retcode,'3',"$name test respects ON_ERROR_STOP");
  is($stdout, $stdout_expect, "$name test STDOUT");
  like($stderr, qr/$stderr_re/, "$name test STDERR");

}

$node->teardown_node;

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 11:37  Daniel Verite <daniel@manitou-mail.org>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-02-04 11:37 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

	Corey Huinker wrote:

[about Ctrl-C]

> That does seem to be the consensus desired behavior. I'm just not sure
> where to handle that. The var "cancel_pressed" shows up in a lot of places.
> Advice?

Probably you don't need to care about cancel_pressed, and
the /if stack could be unwound at the point the SIGINT
handler longjumps to, in mainloop.c:

	/* got here with longjmp */

	/* reset parsing state */
	psql_scan_finish(scan_state);
	psql_scan_reset(scan_state);
	resetPQExpBuffer(query_buf);
	resetPQExpBuffer(history_buf);
	count_eof = 0;
	slashCmdStatus = PSQL_CMD_UNKNOWN;
	prompt_status = PROMPT_READY;
	pset.stmt_lineno = 1;
	cancel_pressed = false;

The check I was suggesting on whether Ctrl+C has been pressed
on an empty line seems harder to implement, because get_interactive()
just calls readline() or fgets(), which block to return when a whole
line is ready. AFAICS psql can't know what was the edit-in-progress
when these functions are interrupted by a signal instead of
returning normally.
But I don't think this check is essential, it could be left to another patch.


Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 16:47  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-04 16:47 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> I noticed that the "barking" is conditional to "success". ISTM that it
> should always "bark" in interactive mode, whether success or not.
>

"success" in those cases means "the expression was a valid boolean", and
non-success cases (should) result in an error being printed regardless of
interactive mode. If you see otherwise, let me know.


>
> While testing it and seeing the code, I agree that it is too
> verbose/redundant. At least remove "active/inactive, ".


Have done so, new patch pending "how-do-I-know-when-input-is-empty" in Ctrl
C.


> - Help text. New block in help text called "Conditionals"
>>
>
> Maybe it could be moved to "Input/Output" renamed as "Input/Output
> Control", or maybe the "Conditionals" section could be moved next to it, it
> seems more logical than after large objects.
>

I put it near the bottom, figuring someone would have a better idea of
where to put it. You did.



> I think that the descriptions are too long. The interactive user can be
> trusted to know what "if/elif/else/endif" mean, or to refer to the full
> documentation otherwise. The point is just to provide a syntax and function
> reminder, not a substitute for the doc. Thus I would suggest shorter
> one-line messages like:
>
>  \if <expr>    begin a new conditional block
>  \elif <expr>  else if in the current conditional block
>  \else         else in current conditional block
>  \endif        end current conditional block
>

+1



>
>>
> Hmmm. Perl is perl. Attached an attempt at improving that, which is
> probably debatable, but at least it is easy to add further tests without
> massive copy-pasting.


+1 that's a good start.


>
>
> - regression tests now have comments to explain purpose
>>
>
> Ok.
>
> Small details about the code:
>
>   +       if (!pset.active_branch && !is_branching_command(cmd) )
>
> Not sure why there is a space before the last closing parenthesis.


+1

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 16:53  Corey Huinker <corey.huinker@gmail.com>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-04 16:53 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> The check I was suggesting on whether Ctrl+C has been pressed
> on an empty line seems harder to implement, because get_interactive()
> just calls readline() or fgets(), which block to return when a whole
> line is ready. AFAICS psql can't know what was the edit-in-progress
> when these functions are interrupted by a signal instead of
> returning normally.
> But I don't think this check is essential, it could be left to another
> patch.
>

Glad I wasn't missing something obvious.
I suppose we could base the behavior on whether there's at least one full
line already buffered.
However, I agree that it can be left to another patch.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-04 21:00  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-04 21:00 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Feb 4, 2017 at 11:53 AM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> The check I was suggesting on whether Ctrl+C has been pressed
>> on an empty line seems harder to implement, because get_interactive()
>> just calls readline() or fgets(), which block to return when a whole
>> line is ready. AFAICS psql can't know what was the edit-in-progress
>> when these functions are interrupted by a signal instead of
>> returning normally.
>> But I don't think this check is essential, it could be left to another
>> patch.
>>
>
> Glad I wasn't missing something obvious.
> I suppose we could base the behavior on whether there's at least one full
> line already buffered.
> However, I agree that it can be left to another patch.
>

v6 patch. highlights:
- error messages are now a bit more terse, following suggestions
- help text is more terse and Conditionals section was moved below Input
Output
- leverage IFSTATE_NONE a bit to fold some not-in-a-branch cases into
existing switch statements, giving flatter, slightly cleaner code and that
addresses expected cases before exceptional ones
- comments highlight which messages are printed in both interactive and
script mode.
- put Fabien's tap test in place verbatim
- No mention of Ctrl-C or PROMPT. Those can be addressed in separate
patches.

There's probably some more consensus building to do over the interactive
messages and comments, and if interactive-ish tests are possible with TAP,
we should add those too.
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..9058897 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,7 @@
 #include "psqlscanslash.h"
 #include "settings.h"
 #include "variables.h"
+#include "fe_utils/psqlscan_int.h"
 
 /*
  * Editable database object types.
@@ -132,7 +133,7 @@ HandleSlashCmds(PsqlScanState scan_state,
 		status = PSQL_CMD_ERROR;
 	}
 
-	if (status != PSQL_CMD_ERROR)
+	if (status != PSQL_CMD_ERROR && pset.active_branch)
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,30 @@ read_connect_arg(PsqlScanState scan_state)
 	return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ * Return true if a boolean value was successfully parsed.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, char *action,
+						bool *result)
+{
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	bool	success = ParseVariableBool(value, action, result);
+	free(value);
+	return success;
+}
+
+/*
+ * Return true if the command given is a branching command.
+ */
+static bool
+is_branching_command(const char *cmd)
+{
+	return (strcmp(cmd, "if") == 0 || strcmp(cmd, "elif") == 0
+			|| strcmp(cmd, "else") == 0 || strcmp(cmd, "endif") == 0);
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +232,17 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd))
+	{
+		if (pset.cur_cmd_interactive)
+			psql_error("inside inactive branch, command ignored.\n");
+
+		/* Continue with an empty buffer as if the command were never read */
+		resetPQExpBuffer(query_buf);
+		psql_scan_reset(scan_state);
+		return status;
+	}
+
 	/*
 	 * \a -- toggle field alignment This makes little sense but we keep it
 	 * around.
@@ -1006,6 +1042,251 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth
+				 */
+				psql_error("new \\if is invalid, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth
+					 */
+					psql_error("\\elif is invalid, "
+								"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, \n"
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, \n"
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, \n"
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..4b30675 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -296,8 +313,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 +375,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 +384,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 +447,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 +468,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..1c8c3c4
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 15;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $tests = [
+	[ "\\if invalid_expression\n\\endif\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test respects ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] 0001.if_endif.v6.diff (27.7K, ../../CADkLM=cxEPJW+67cnam_on_fH3eQaekTS-8=Aa-JM+4ETr71Kw@mail.gmail.com/3-0001.if_endif.v6.diff)
  download | inline diff:
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..9058897 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,7 @@
 #include "psqlscanslash.h"
 #include "settings.h"
 #include "variables.h"
+#include "fe_utils/psqlscan_int.h"
 
 /*
  * Editable database object types.
@@ -132,7 +133,7 @@ HandleSlashCmds(PsqlScanState scan_state,
 		status = PSQL_CMD_ERROR;
 	}
 
-	if (status != PSQL_CMD_ERROR)
+	if (status != PSQL_CMD_ERROR && pset.active_branch)
 	{
 		/* eat any remaining arguments after a valid command */
 		/* note we suppress evaluation of backticks here */
@@ -194,6 +195,30 @@ read_connect_arg(PsqlScanState scan_state)
 	return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ * Return true if a boolean value was successfully parsed.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, char *action,
+						bool *result)
+{
+	char	*value = psql_scan_slash_option(scan_state,
+											OT_NORMAL, NULL, false);
+	bool	success = ParseVariableBool(value, action, result);
+	free(value);
+	return success;
+}
+
+/*
+ * Return true if the command given is a branching command.
+ */
+static bool
+is_branching_command(const char *cmd)
+{
+	return (strcmp(cmd, "if") == 0 || strcmp(cmd, "elif") == 0
+			|| strcmp(cmd, "else") == 0 || strcmp(cmd, "endif") == 0);
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +232,17 @@ exec_command(const char *cmd,
 								 * failed */
 	backslashResult status = PSQL_CMD_SKIP_LINE;
 
+	if (!pset.active_branch && !is_branching_command(cmd))
+	{
+		if (pset.cur_cmd_interactive)
+			psql_error("inside inactive branch, command ignored.\n");
+
+		/* Continue with an empty buffer as if the command were never read */
+		resetPQExpBuffer(query_buf);
+		psql_scan_reset(scan_state);
+		return status;
+	}
+
 	/*
 	 * \a -- toggle field alignment This makes little sense but we keep it
 	 * around.
@@ -1006,6 +1042,251 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth
+				 */
+				psql_error("new \\if is invalid, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth
+					 */
+					psql_error("\\elif is invalid, "
+								"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, \n"
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, \n"
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, \n"
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..4b30675 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -296,8 +313,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 +375,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 +384,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 +447,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 +468,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..1c8c3c4
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 15;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $tests = [
+	[ "\\if invalid_expression\n\\endif\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test respects ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-05 07:55  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-05 07:55 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

>> The check I was suggesting on whether Ctrl+C has been pressed
>>> on an empty line seems harder to implement, because get_interactive()
>>> just calls readline() or fgets(), which block to return when a whole
>>> line is ready. AFAICS psql can't know what was the edit-in-progress
>>> when these functions are interrupted by a signal instead of
>>> returning normally.
>>> But I don't think this check is essential, it could be left to another
>>> patch.
>>
>> Glad I wasn't missing something obvious.
>> I suppose we could base the behavior on whether there's at least one full
>> line already buffered.
>> However, I agree that it can be left to another patch.

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

Comments about v6:

> - error messages are now a bit more terse, following suggestions

Ok.

> - help text is more terse and Conditionals section was moved below Input
> Output

Ok.

> - leverage IFSTATE_NONE a bit to fold some not-in-a-branch cases into
> existing switch statements, giving flatter, slightly cleaner code and that
> addresses expected cases before exceptional ones

Code looks ok.

> - comments highlight which messages are printed in both interactive and
> script mode.

Yep.

> - put Fabien's tap test in place verbatim

Hmmm. That was really just a POC... I would suggest some more tests, eg:

   # elif error
   "\\if false\n\\elif error\n\\endif\n"

   # ignore commands on error (stdout must be empty)
   "\\if error\n\\echo NO\n\\else\n\\echo NO\n\\endif\n"

Also there is an empty line before the closing } of the while loop.

> - No mention of Ctrl-C or PROMPT. Those can be addressed in separate
> patches.

I think that Ctrl-C resetting the stack must be addressed in this patch. 
Trying to be more intelligent/incremental on Ctrl-C can wait for another 
time.

> There's probably some more consensus building to do over the interactive
> messages and comments,

Barking is now quite more verbose (?), but at least it is clear about the 
status and what is expected. I noticed that it is now always on, whether 
an error occured or not, which is ok with me.

> and if interactive-ish tests are possible with TAP, we should add those 
> too.

Good point. It seems that it is decided based on "source == stdin" plus 
checking whether both stdin/stdout are on terminal. Allowing to work 
around the later requires some more infrastructure to force "notty" (yuk, 
a negative variable tested negatively...) to false whatever, which does 
not seem to exist. So this is for another time.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 06:39  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 06:39 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

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

And so it shall be.


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

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

Anyway, here's the Ctrl-C behavior:

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


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

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


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 09:36  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-06 09:36 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


>>   # elif error
>>   "\\if false\n\\elif error\n\\endif\n"
>>
>>   # ignore commands on error (stdout must be empty)
>>   "\\if error\n\\echo NO\n\\else\n\\echo NO\n\\endif\n"
>
> Those are already in the regression (around line 2763 of
> expected/psql.out), are you saying we should have them in TAP as well?
> Should we only do TAP tests?

Ok. so, maybe just the first one. The idea would be to cover more cases of 
on error stop and check that it indeed stopped.

Find attached a small patch to improve tap tests, which also checks that 
psql really exited by checking that nothing is printed afterwards.

Also, for some reason there were \\n instead of \n in some place, it was 
working because the first command induced the error.

> Anyway, here's the Ctrl-C behavior:

Ok. Basically it moves up each time Ctrl-C is called. Fine.

The future improvement would be to do that if the current input line was 
empty, otherwise only the current input line would be cleaned up.

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

Ok. Bar from minor tests improvements, this looks pretty much ok to me.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 09:38  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-06 09:38 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Find attached a small patch to improve tap tests, which also checks that psql 
> really exited by checking that nothing is printed afterwards.

<Sigh>. It is better with the attachement.

-- 
Fabien.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-diff] tests.patch (1.8K, ../../alpine.DEB.2.20.1702061038090.10745@lancre/2-tests.patch)
  download | inline diff:
diff --git a/src/bin/psql/t/001_if.pl b/src/bin/psql/t/001_if.pl
index 68c9b15..a703cab 100644
--- a/src/bin/psql/t/001_if.pl
+++ b/src/bin/psql/t/001_if.pl
@@ -4,7 +4,7 @@ use warnings;
 use Config;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 18;
 
 #
 # test invalid \if respects ON_ERROR_STOP
@@ -14,12 +14,14 @@ $node->init;
 $node->start;
 
 my $tests = [
-	[ "\\if invalid_expression\n\\endif\n", '', 'boolean expected', 'syntax error' ],
+    # syntax errors
+	[ "\\if invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
 	# unmatched checks
-	[ "\\if true\\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
-	[ "\\elif true\\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
-	[ "\\else\\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
-	[ "\\endif\\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\n\\echo NO\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
 ];
 
 # 3 checks per tests
@@ -29,7 +31,7 @@ for my $test (@$tests) {
   my $retcode = $node->psql('postgres', $script,
 		stdout => \$stdout, stderr => \$stderr,
 		on_error_stop => 1);
-  is($retcode,'3',"$name test respects ON_ERROR_STOP");
+  is($retcode,'3',"$name test ON_ERROR_STOP");
   is($stdout, $stdout_expect, "$name test STDOUT");
   like($stderr, qr/$stderr_re/, "$name test STDERR");
 }


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 16:21  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 16:21 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> Find attached a small patch to improve tap tests, which also checks that
>> psql really exited by checking that nothing is printed afterwards.
>>
>
>
Do you think the TAP tests would benefit from having the input described in
a q(...) block rather than a string?

q(\if false
\echo a
\elif invalid
\echo b
\endif
\echo c
)


It's a lot more lines, obviously, but it might make what is being tested
clearer.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 18:46  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 18:46 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 6, 2017 at 11:21 AM, Corey Huinker <corey.huinker@gmail.com>
wrote:

>
>> Find attached a small patch to improve tap tests, which also checks that
>>> psql really exited by checking that nothing is printed afterwards.
>>>
>>
>>
> Do you think the TAP tests would benefit from having the input described
> in a q(...) block rather than a string?
>
> q(\if false
> \echo a
> \elif invalid
> \echo b
> \endif
> \echo c
> )
>
>
> It's a lot more lines, obviously, but it might make what is being tested
> clearer.
>
>
It occurred to me that the part of this patch most important to casual
users would be the printed messages at various states. I've enumerated
those below, along with the circumstances under which the user would see
them.

The following messages are for interactive and script users. They are also
errors which respect ON_ERROR_STOP.
-------------------

\if statement which had an invalid boolean expression:

new \if is invalid, ignoring commands until next \endif


\elif was in a proper \if block, and not after the true block, but boolean
expression was invalid:

\elif is invalid, ignoring commands until next \endif


\elif statement after an \else

encountered \elif after \else


\elif statement outside of an \if block [*]

encountered un-matched \elif


\else outside of an \if

encountered un-matched \else


\else after an \else

encountered \else after \else


\endif statement outside of an \if block

encountered un-matched \endif


Input file ends with unresolved \if blocks

found EOF before closing \endif(s)


The following are interactive-only non-error informational messages.
-------------

\if statement which parsed to true:

new \if is true, executing commands


\if statement which parsed to false:

new \if is false, ignoring commands until next \elif, \else, or \endif

\if statement while already in a false/invalid block:

new \if is inside ignored block, ignoring commands until next \endif


\elif statement immediately after the true \if or \elif

\elif is after true block, ignoring commands until next \endif


\elif statement within a false block or subsequent elif after the first
ignored elif

\elif is inside ignored block, ignoring commands until next \endif


\elif was evaluated, was true

\elif is true, executing commands


\elif was evaluated, was false

\elif is false, ignoring commands until next \elif, \else, or \endif


\else statement in an ignored block or after the true block was found:

\else after true condition or in ignored block, ignoring commands until
next \endif


\else statement and all previous blocks were false

\else after all previous conditions false, executing commands


\endif statement ending only \if on the stack

exited \if, executing commands


\endif statement where last block was false but parent block is also false:

exited \\if to false parent branch, ignoring commands until next \endif


\endif statement where last block was true and parent is true

exited \\if to true parent branch, continuing executing commands


\endif statement where last block was false but parent is true

exited \\if to true parent branch, resuming executing commands


Script is currently in a false (or invalid) branch, and user entered a
command other than if/elif/endif:

inside inactive branch, command ignored.


Script currently in a false branch, and user entered a query:

inside inactive branch, query ignored. use \endif to exit current branch.


User in an \if branch and pressed ^C, with no more branches remaining:

escaped \\if, executing commands


User in an \if branch and pressed ^C, but parent branch was false:

escaped \\if to false parent branch, ignoring commands until next \endif


User in a true \if branch and pressed ^C, parent branch true

escaped \\if to true parent branch, continuing executing commands


User in a false \if branch and pressed ^C, parent branch true

escaped \if to true parent branch, resuming executing commands



Notes:
--------

The text for ignored commands vs ignored queries is different.

The text for all the Ctrl-C messages re-uses the \endif messages, but are
"escaped" instead of "exited".

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 19:32  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-06 19:32 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Do you think the TAP tests would benefit from having the input described 
> in a q(...) block rather than a string?

My 0.02€: Not really, so I would not bother. It breaks perl indentation 
and logic for a limited benefit. Maybe add comments if you feel that a 
test case is unclear.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 19:50  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 19:50 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

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

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


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


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 20:27  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-06 20:27 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Consolidated Fabien's TAP test additions with v7, in case anyone else wants
> to be reviewing.

Patch applies (with "patch"), make check ok, psql tap test ok.

I did some more tests. I found a subtlety that I missed before: when 
running under ON_ERROR_STOP, messages are not fully consistent:

  sh> cat test.sql
  \set ON_ERROR_STOP on
  \if error
    \echo NO
  \endif
  \echo NO

  sh> ./psql < test.sql
  SET
  # ok
  unrecognized value "error" for "\if <expr>": boolean expected
  # ok
  new \if is invalid, ignoring commands until next \endif
  # hmmm... but it does not, it is really stopping immediately...
  found EOF before closing \endif(s)
  # no, it has just stopped before EOF because of the error...

Also I'm not quite sure why psql decided that it is in interactive mode 
above, its stdin is a file, but why not.

The issue is made more explicit with -f:

  sh> ./psql -f test.sql
  SET
  psql:test.sql:2: unrecognized value "error" for "\if <expr>": boolean expected
  psql:test.sql:2: new \if is invalid, ignoring commands until next \endif
  psql:test.sql:2: found EOF before closing \endif(s)

It stopped on line 2, which is expected, but it was not on EOF.

I think that the message when stopping should be ", stopping as required 
by ON_ERROR_STOP" or something like that instead of ", ignoring...", and 
the EOF message should not be printed at all in this case.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 20:43  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 20:43 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> I did some more tests. I found a subtlety that I missed before: when
> running under ON_ERROR_STOP, messages are not fully consistent:
>
>  sh> cat test.sql
>  \set ON_ERROR_STOP on
>  \if error
>    \echo NO
>  \endif
>  \echo NO
>
>  sh> ./psql < test.sql
>  SET
>  # ok
>  unrecognized value "error" for "\if <expr>": boolean expected
>  # ok


That's straight from ParseVariableBool, and we can keep that or suppress
it. Whatever we do, we should do it with the notion that more complex
expressions will eventually be allowed, but they'll still have to resolve
to something that's a text boolean.


>
>  new \if is invalid, ignoring commands until next \endif
>  # hmmm... but it does not, it is really stopping immediately...

 found EOF before closing \endif(s)
>  # no, it has just stopped before EOF because of the error...
>

Yeah, chattiness caught up to us here. Both of these messages can be
suppressed, I think.


>
> Also I'm not quite sure why psql decided that it is in interactive mode
> above, its stdin is a file, but why not.
>
> The issue is made more explicit with -f:
>
>  sh> ./psql -f test.sql
>  SET
>  psql:test.sql:2: unrecognized value "error" for "\if <expr>": boolean
> expected
>  psql:test.sql:2: new \if is invalid, ignoring commands until next \endif
>  psql:test.sql:2: found EOF before closing \endif(s)
>
> It stopped on line 2, which is expected, but it was not on EOF.
>
> I think that the message when stopping should be ", stopping as required
> by ON_ERROR_STOP" or something like that instead of ", ignoring...", and
> the EOF message should not be printed at all in this case.


I agree, and will look into making that happen. Thanks for the test case.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-06 22:49  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-06 22:49 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 6, 2017 at 3:43 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> I did some more tests. I found a subtlety that I missed before: when
>> running under ON_ERROR_STOP, messages are not fully consistent:
>>
>>  sh> cat test.sql
>>  \set ON_ERROR_STOP on
>>  \if error
>>    \echo NO
>>  \endif
>>  \echo NO
>>
>>  sh> ./psql < test.sql
>>  SET
>>  # ok
>>  unrecognized value "error" for "\if <expr>": boolean expected
>>  # ok
>
>
> That's straight from ParseVariableBool, and we can keep that or suppress
> it. Whatever we do, we should do it with the notion that more complex
> expressions will eventually be allowed, but they'll still have to resolve
> to something that's a text boolean.
>
>
>>
>>  new \if is invalid, ignoring commands until next \endif
>>  # hmmm... but it does not, it is really stopping immediately...
>
>  found EOF before closing \endif(s)
>>  # no, it has just stopped before EOF because of the error...
>>
>
> Yeah, chattiness caught up to us here. Both of these messages can be
> suppressed, I think.
>
>
>>
>> Also I'm not quite sure why psql decided that it is in interactive mode
>> above, its stdin is a file, but why not.
>>
>> The issue is made more explicit with -f:
>>
>>  sh> ./psql -f test.sql
>>  SET
>>  psql:test.sql:2: unrecognized value "error" for "\if <expr>": boolean
>> expected
>>  psql:test.sql:2: new \if is invalid, ignoring commands until next \endif
>>  psql:test.sql:2: found EOF before closing \endif(s)
>>
>> It stopped on line 2, which is expected, but it was not on EOF.
>>
>> I think that the message when stopping should be ", stopping as required
>> by ON_ERROR_STOP" or something like that instead of ", ignoring...", and
>> the EOF message should not be printed at all in this case.
>
>
> I agree, and will look into making that happen. Thanks for the test case.
>
>

I suppressed the endif-balance checking in cases where we're in an
already-failed situation.
In cases where there was a boolean parsing failure, and ON_ERROR_STOP is
on, the error message no longer speak of a future which the session does
not have. I could left the ParseVariableBool() message as the only one, but
wasn't sure that that was enough of an error message on its own.
Added the test case to the existing tap tests. Incidentally, the tap tests
aren't presently fooled into thinking they're interactive.

$ cat test2.sql
\if error
    \echo NO
\endif
\echo NOPE
$ psql test < test2.sql -v ON_ERROR_STOP=0
unrecognized value "error" for "\if <expr>": boolean expected
new \if is invalid, ignoring commands until next \endif
NOPE
$ psql test < test2.sql -v ON_ERROR_STOP=1
unrecognized value "error" for "\if <expr>": boolean expected
new \if is invalid.
$ psql test -f test2.sql -v ON_ERROR_STOP=0
psql:test2.sql:1: unrecognized value "error" for "\if <expr>": boolean
expected
psql:test2.sql:1: new \if is invalid, ignoring commands until next \endif
NOPE
$ psql test -f test2.sql -v ON_ERROR_STOP=1
psql:test2.sql:1: unrecognized value "error" for "\if <expr>": boolean
expected
psql:test2.sql:1: new \if is invalid.


Revised cumulative patch attached for those playing along at home.
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..f10d7ac 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,259 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth. suppress this error in
+				 * cases where the script is already going to terminate.
+				 */
+				if (pset.on_error_stop)
+					psql_error("new \\if is invalid.\n");
+				else
+					psql_error("new \\if is invalid, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth. suppress this error in
+					 * cases where the script is already going to terminate.
+					 */
+					if (pset.on_error_stop)
+						psql_error("\\elif is invalid.\n");
+					else
+						psql_error("\\elif is invalid, "
+									"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, "
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, "
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, "
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..b599581 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +139,35 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					bool was_active = pset.active_branch;
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+
+					if (psqlscan_branch_stack_empty(scan_state))
+						/* no more branches */
+						psql_error("escaped \\if, executing commands\n");
+					else if (!pset.active_branch)
+						/* was ignoring, still ignoring */
+						psql_error("escaped \\if to false parent branch, "
+								"ignoring commands until next \\endif\n");
+					else if (was_active)
+						/* was true, still true */
+						psql_error("escaped \\if to true parent branch, "
+								"continuing executing commands\n");
+					else
+						/* was false, is true again */
+						psql_error("escaped \\if to true parent branch, "
+								"resuming executing commands\n");
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +341,8 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				success = send_query(query_buf->data);
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
 
@@ -358,7 +403,7 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					success = send_query(query_buf->data);
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +412,7 @@ MainLoop(FILE *source)
 						previous_buf = query_buf;
 						query_buf = swap_buf;
 					}
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -429,8 +475,7 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		success = send_query(query_buf->data);
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +496,19 @@ 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
+		&& !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..88f3013
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,41 @@
+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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\n\\echo NO\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+	# error stop messages
+	[ "\\set ON_ERROR_STOP on\n\\if error\n\\echo NO\n\\endif\n\\echo NO\n", '', 'new.*if is invalid.', 'invalid if'],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] 0001.if_endif.v9.diff (29.5K, ../../CADkLM=eVD+SXB0LD6J1pn+NiWn4sLqR7RUpMpd8FPnvyToyWjw@mail.gmail.com/3-0001.if_endif.v9.diff)
  download | inline diff:
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..f10d7ac 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,259 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth. suppress this error in
+				 * cases where the script is already going to terminate.
+				 */
+				if (pset.on_error_stop)
+					psql_error("new \\if is invalid.\n");
+				else
+					psql_error("new \\if is invalid, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth. suppress this error in
+					 * cases where the script is already going to terminate.
+					 */
+					if (pset.on_error_stop)
+						psql_error("\\elif is invalid.\n");
+					else
+						psql_error("\\elif is invalid, "
+									"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, "
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, "
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, "
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..b599581 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +139,35 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					bool was_active = pset.active_branch;
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+
+					if (psqlscan_branch_stack_empty(scan_state))
+						/* no more branches */
+						psql_error("escaped \\if, executing commands\n");
+					else if (!pset.active_branch)
+						/* was ignoring, still ignoring */
+						psql_error("escaped \\if to false parent branch, "
+								"ignoring commands until next \\endif\n");
+					else if (was_active)
+						/* was true, still true */
+						psql_error("escaped \\if to true parent branch, "
+								"continuing executing commands\n");
+					else
+						/* was false, is true again */
+						psql_error("escaped \\if to true parent branch, "
+								"resuming executing commands\n");
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +341,8 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				success = send_query(query_buf->data);
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
 
@@ -358,7 +403,7 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					success = send_query(query_buf->data);
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +412,7 @@ MainLoop(FILE *source)
 						previous_buf = query_buf;
 						query_buf = swap_buf;
 					}
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -429,8 +475,7 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		success = send_query(query_buf->data);
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +496,19 @@ 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
+		&& !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..88f3013
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,41 @@
+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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\n\\echo NO\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+	# error stop messages
+	[ "\\set ON_ERROR_STOP on\n\\if error\n\\echo NO\n\\endif\n\\echo NO\n", '', 'new.*if is invalid.', 'invalid if'],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-07 07:36  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-07 07:36 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> In cases where there was a boolean parsing failure, and ON_ERROR_STOP is
> on, the error message no longer speak of a future which the session does
> not have. I could left the ParseVariableBool() message as the only one, but
> wasn't sure that that was enough of an error message on its own.
> Added the test case to the existing tap tests. Incidentally, the tap tests
> aren't presently fooled into thinking they're interactive.

Yes.

> Revised cumulative patch attached for those playing along at home.

Nearly there...

It seems that ON_ERROR_STOP is mostly ignored by design when in 
interactive mode, probably because it is nicer not to disconnect the user 
who is actually typing things on a terminal.

"""
   ON_ERROR_STOP

By default, command processing continues after an error. When this 
variable is set to on, processing will instead stop immediately. In 
interactive mode, psql will return to the command prompt; otherwise, psql 
will exit, returning error code 3 to distinguish this case from fatal 
error conditions, which are reported using error code 1.
"""

So, you must check for interactive as well when shortening the message, 
and adapting it accordingly, otherwise on gets the wrong message in 
interactive mode:

    bash> ./psql -v ON_ERROR_STOP=1
    psql (10devel, server 9.6.1)
    Type "help" for help.

    calvin=# \if error
    unrecognized value "error" for "\if <expr>": boolean expected
    new \if is invalid.
    calvin=# -- not stopped, but the stack has been cleaned up, I think

Basically it seems that there are 4 cases and 2 behaviors:

  - on_error_stop && scripting:
      actually exit on error

  - on_error_stop && interactive, !on_error_stop whether scripting or not:
      keep going, possibly with nesting checks?

The problem is that currently interactive behavior cannot be tested 
automatically.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-07 18:49  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-07 18:49 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> It seems that ON_ERROR_STOP is mostly ignored by design when in
> interactive mode, probably because it is nicer not to disconnect the user
> who is actually typing things on a terminal.
>
> """
>   ON_ERROR_STOP
>
> By default, command processing continues after an error. When this
> variable is set to on, processing will instead stop immediately. In
> interactive mode, psql will return to the command prompt; otherwise, psql
> will exit, returning error code 3 to distinguish this case from fatal error
> conditions, which are reported using error code 1.
> """
>

This was my previous understanding of ON_ERROR_STOP. Somewhere in the
course of developing this patch I lost that. Glad to have it back.

The only changes I made were to invalid booleans on if/elif, and the final
branch balancing check won't set status to EXIT_USER unless it's
non-interactive and ON_ERROR_STOP = on.

> \if true
new \if is true, executing commands
> \endif
exited \if, executing commands
> \if false
new \if is false, ignoring commands until next \elif, \else, or \endif
> \endif
exited \if, executing commands
> \if error
unrecognized value "error" for "\if <expr>": boolean expected
new \if is invalid, ignoring commands until next \endif
> \echo foo
inside inactive branch, command ignored.
> ^C
escaped \if, executing commands
> \echo foo
foo
> \endif
encountered un-matched \endif
>
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..63ddf0a 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,259 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth. suppress this error in
+				 * cases where the script is already going to terminate.
+				 */
+				if (pset.on_error_stop && !pset.cur_cmd_interactive)
+					psql_error("new \\if is invalid.\n");
+				else
+					psql_error("new \\if is invalid, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth. suppress this error in
+					 * cases where the script is already going to terminate.
+					 */
+					if (pset.on_error_stop && !pset.cur_cmd_interactive)
+						psql_error("\\elif is invalid.\n");
+					else
+						psql_error("\\elif is invalid, "
+									"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, "
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, "
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, "
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..de93b52 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +139,35 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					bool was_active = pset.active_branch;
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+
+					if (psqlscan_branch_stack_empty(scan_state))
+						/* no more branches */
+						psql_error("escaped \\if, executing commands\n");
+					else if (!pset.active_branch)
+						/* was ignoring, still ignoring */
+						psql_error("escaped \\if to false parent branch, "
+								"ignoring commands until next \\endif\n");
+					else if (was_active)
+						/* was true, still true */
+						psql_error("escaped \\if to true parent branch, "
+								"continuing executing commands\n");
+					else
+						/* was false, is true again */
+						psql_error("escaped \\if to true parent branch, "
+								"resuming executing commands\n");
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +341,8 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				success = send_query(query_buf->data);
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
 
@@ -358,7 +403,7 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					success = send_query(query_buf->data);
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +412,7 @@ MainLoop(FILE *source)
 						previous_buf = query_buf;
 						query_buf = swap_buf;
 					}
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -429,8 +475,7 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		success = send_query(query_buf->data);
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +496,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..88f3013
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,41 @@
+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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\n\\echo NO\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+	# error stop messages
+	[ "\\set ON_ERROR_STOP on\n\\if error\n\\echo NO\n\\endif\n\\echo NO\n", '', 'new.*if is invalid.', 'invalid if'],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] 0001.if_endif.v10.diff (29.6K, ../../CADkLM=futLcQrjJ_-=NnMah56snegE3VXvFEvne=KQQ-6Ye8NQ@mail.gmail.com/3-0001.if_endif.v10.diff)
  download | inline diff:
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..63ddf0a 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,259 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \if <expr> is the beginning of an \if..\endif block.
+	 * <expr> must be a valid boolean expression, or the whole block will be
+	 * ignored.
+	 * If this \if is itself a part of a branch that is false/ignored, it too
+	 * will automatically be ignored.
+	 */
+	else if (strcmp(cmd, "if") == 0)
+	{
+		ifState new_if_state = IFSTATE_IGNORED;
+		/*
+		 * only evaluate the expression for truth if the underlying
+		 * branch is active
+		 */
+		if (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				if (if_true)
+				{
+					new_if_state = IFSTATE_TRUE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is true, executing commands\n");
+					pset.active_branch = true;
+				}
+				else
+				{
+					new_if_state = IFSTATE_FALSE;
+					if (pset.cur_cmd_interactive)
+						psql_error("new \\if is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+					pset.active_branch = false;
+				}
+			}
+			else
+			{
+				/*
+				 * show this error in both interactive and script, because the
+				 * session is now in a state where all blocks will be ignored
+				 * regardless of expression truth. suppress this error in
+				 * cases where the script is already going to terminate.
+				 */
+				if (pset.on_error_stop && !pset.cur_cmd_interactive)
+					psql_error("new \\if is invalid.\n");
+				else
+					psql_error("new \\if is invalid, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+			}
+		}
+		else
+		{
+			if (pset.cur_cmd_interactive)
+				psql_error("new \\if is inside ignored block, "
+						"ignoring commands until next \\endif\n");
+			pset.active_branch = false;
+		}
+
+		psqlscan_branch_push(scan_state,new_if_state);
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \elif <expr> is part of an \if..\endif block
+	 * <expr> will only be evalated for boolean truth if no previous
+	 * \if or \endif in the block has evaluated to true and the \if..\endif
+	 * block is not itself being ignored.
+	 * in the event that <expr> does not conform to a proper boolean expression,
+	 * all following statements in the block will be ignored until \endif is
+	 * encountered.
+	 *
+	 */
+	else if (strcmp(cmd, "elif") == 0)
+	{
+		bool elif_true = false;
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, do nothing:
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is inside ignored block, "
+							"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of active branch
+				 * do not evaluate expression, just skip
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\elif is after true block, "
+							"ignoring commands until next \\endif\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+				pset.active_branch = false;
+				break;
+			case IFSTATE_FALSE:
+				/*
+				 * have not yet found a true block in this if-endif,
+				 * determine if this section is true or not.
+				 * variable expansion must be temporarily turned back
+				 * on to read the boolean expression.
+				 */
+				pset.active_branch = true;
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					if (elif_true)
+					{
+						psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is true, executing commands\n");
+						pset.active_branch = false;
+					}
+					else
+					{
+						if (pset.cur_cmd_interactive)
+							psql_error("\\elif is false, ignoring commands "
+									"until next \\elif, \\else, or \\endif\n");
+						pset.active_branch = false;
+					}
+				}
+				else
+				{
+					/*
+					 * show this error in both interactive and script, because the
+					 * session is now in a state where all blocks will be ignored
+					 * regardless of expression truth. suppress this error in
+					 * cases where the script is already going to terminate.
+					 */
+					if (pset.on_error_stop && !pset.cur_cmd_interactive)
+						psql_error("\\elif is invalid.\n");
+					else
+						psql_error("\\elif is invalid, "
+									"ignoring commands until next \\endif\n");
+					psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+					pset.active_branch = false;
+				}
+				pset.active_branch = psqlscan_branch_active(scan_state);
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\elif after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			case IFSTATE_NONE:
+				/* no if to elif from */
+				psql_error("encountered un-matched \\elif\n");
+				success = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \else is part of an \if..\endif block
+	 * the statements within an \else branch will only be executed if
+	 * all previous \if and \endif expressions evaluated to false
+	 * and the block was not itself being ignored.
+	 */
+	else if (strcmp(cmd, "else") == 0)
+	{
+		switch (psqlscan_branch_get_state(scan_state))
+		{
+			case IFSTATE_TRUE:
+			case IFSTATE_IGNORED:
+				/*
+				 * either just finished true section of an active branch,
+				 * or whole branch was inactive. either way, be on the
+				 * lookout for any invalid \endif or \else commands
+				 */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after true condition or in ignored block, "
+								"ignoring commands until next \\endif\n");
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_FALSE);
+				break;
+			case IFSTATE_FALSE:
+				/* just finished false section of an active branch */
+				if (pset.cur_cmd_interactive)
+					psql_error("\\else after all previous conditions false, "
+								"executing commands\n");
+				psqlscan_branch_set_state(scan_state, IFSTATE_ELSE_TRUE);
+				pset.active_branch = true;
+				break;
+			case IFSTATE_NONE:
+				/* no if to else from */
+				psql_error("encountered un-matched \\else\n");
+				success = false;
+				break;
+			case IFSTATE_ELSE_TRUE:
+			case IFSTATE_ELSE_FALSE:
+				psql_error("encountered \\else after \\else\n");
+				success = false;
+				pset.active_branch = false;
+				break;
+			default:
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \endif - closing statment of an \if...\endif block
+	 */
+	else if (strcmp(cmd, "endif") == 0)
+	{
+		/*
+		 * get rid of this ifstate element and look at the previous
+		 * one, if any
+		 */
+		if (psqlscan_branch_pop(scan_state))
+		{
+			bool was_active = pset.active_branch;
+			pset.active_branch = psqlscan_branch_active(scan_state);
+
+			if (pset.cur_cmd_interactive)
+			{
+				if (psqlscan_branch_stack_empty(scan_state))
+					/* no more branches */
+					psql_error("exited \\if, executing commands\n");
+				else if (!pset.active_branch)
+					/* was ignoring, still ignoring */
+					psql_error("exited \\if to false parent branch, "
+							"ignoring commands until next \\endif\n");
+				else if (was_active)
+					/* was true, still true */
+					psql_error("exited \\if to true parent branch, "
+							"continuing executing commands\n");
+				else
+					/* was false, is true again */
+					psql_error("exited \\if to true parent branch, "
+							"resuming executing commands\n");
+			}
+		}
+		else
+		{
+			psql_error("encountered un-matched \\endif\n");
+			success = false;
+		}
+
+		psql_scan_reset(scan_state);
+	}
+
 	/* \l is list databases */
 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..de93b52 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 	psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+	/* execute query if branch is active */
+	if (pset.active_branch)
+		return SendQuery(query);
+
+	if (pset.cur_cmd_interactive)
+		psql_error("inside inactive branch, query ignored. "
+					"use \\endif to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +139,35 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					bool was_active = pset.active_branch;
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+
+					if (psqlscan_branch_stack_empty(scan_state))
+						/* no more branches */
+						psql_error("escaped \\if, executing commands\n");
+					else if (!pset.active_branch)
+						/* was ignoring, still ignoring */
+						psql_error("escaped \\if to false parent branch, "
+								"ignoring commands until next \\endif\n");
+					else if (was_active)
+						/* was true, still true */
+						psql_error("escaped \\if to true parent branch, "
+								"continuing executing commands\n");
+					else
+						/* was false, is true again */
+						psql_error("escaped \\if to true parent branch, "
+								"resuming executing commands\n");
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +341,8 @@ MainLoop(FILE *source)
 					line_saved_in_history = true;
 				}
 
-				/* execute query */
-				success = SendQuery(query_buf->data);
+				success = send_query(query_buf->data);
+
 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
 				pset.stmt_lineno = 1;
 
@@ -358,7 +403,7 @@ MainLoop(FILE *source)
 
 				if (slashCmdStatus == PSQL_CMD_SEND)
 				{
-					success = SendQuery(query_buf->data);
+					success = send_query(query_buf->data);
 
 					/* transfer query to previous_buf by pointer-swapping */
 					{
@@ -367,6 +412,7 @@ MainLoop(FILE *source)
 						previous_buf = query_buf;
 						query_buf = swap_buf;
 					}
+
 					resetPQExpBuffer(query_buf);
 
 					/* flush any paren nesting info after forced send */
@@ -429,8 +475,7 @@ MainLoop(FILE *source)
 		if (pset.cur_cmd_interactive)
 			pg_send_history(history_buf);
 
-		/* execute query */
-		success = SendQuery(query_buf->data);
+		success = send_query(query_buf->data);
 
 		if (!success && die_on_error)
 			successResult = EXIT_USER;
@@ -451,6 +496,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..88f3013
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,41 @@
+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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 'unmatched \elif' ],
+	[ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+	[ "\\endif\n\\echo NO\n", '', 'encountered un-matched.*endif', 'unmatched \endif' ],
+	# error stop messages
+	[ "\\set ON_ERROR_STOP on\n\\if error\n\\echo NO\n\\endif\n\\echo NO\n", '', 'new.*if is invalid.', 'invalid if'],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+		stdout => \$stdout, stderr => \$stderr,
+		on_error_stop => 1);
+  is($retcode,'3',"$name test ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
 	psql_scan_reset(state);
 
+	state->branch_stack = NULL;
+
 	return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
 	yylex_destroy(state->scanner);
 
+	while (state->branch_stack != NULL)
+	{
+		IfStackElem *p = state->branch_stack;
+		state->branch_stack = state->branch_stack->next;
+		free(p);
+	}
+
 	free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
 		psqlscan_emit(state, txt, len);
 	}
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+	return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return IFSTATE_NONE;
+	return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+	ifState s = psqlscan_branch_get_state(state);
+	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+	IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = state->branch_stack;
+	state->branch_stack = p;
+	return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+	if (psqlscan_branch_stack_empty(state))
+		return false;
+	state->branch_stack->if_state = new_state;
+	return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+	IfStackElem *p = state->branch_stack;
+	if (!p)
+		return false;
+	state->branch_stack = state->branch_stack->next;
+	free(p);
+	return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
 	struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+	IFSTATE_NONE = 0,	/* Not currently in an \if block */
+	IFSTATE_TRUE,		/* currently in an \if or \elif which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_FALSE,		/* currently in an \if or \elif which is false
+						 * but no true branch has yet been seen,
+						 * and all parent branches (if any) are true */
+	IFSTATE_IGNORED,	/* currently in an \elif which follows a true \if
+						 * or the whole \if is a child of a false parent */
+	IFSTATE_ELSE_TRUE,	/* currently in an \else which is true
+						 * and all parent branches (if any) are true */
+	IFSTATE_ELSE_FALSE	/* currently in an \else which is false or ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+	ifState		if_state;
+	struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
 	 * Callback functions provided by the program making use of the lexer.
 	 */
 	const PsqlScanCallbacks *callbacks;
+
+	/*
+	 * \if branch state variables
+	 */
+	IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
 						 const char *txt, int len,
 						 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+									ifState new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+all true
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+first thing true
+\else
+	\echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+second thing true
+\else
+	\echo 'should not print #5-1'
+\endif
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected
+new \if is invalid, ignoring commands until next \endif
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- test a large nested if using a variety of true-equivalents
+\if true
+	\if 1
+		\if yes
+			\if on
+				\echo 'all true'
+			\else
+				\echo 'should not print #1-1'
+			\endif
+		\else
+			\echo 'should not print #1-2'
+		\endif
+	\else
+		\echo 'should not print #1-3'
+	\endif
+\else
+	\echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+	\echo 'should not print #2-1'
+\elif 0
+	\echo 'should not print #2-2'
+\elif no
+	\echo 'should not print #2-3'
+\elif off
+	\echo 'should not print #2-4'
+\else
+	\echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+	\echo 'first thing true'
+\else
+	\echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+	\echo 'should not print #4-1'
+\elif true
+	\echo 'second thing true'
+\else
+	\echo 'should not print #5-1'
+\endif
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+	\echo 'should not print #6-1'
+\else
+	\echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-07 21:06  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-07 21:06 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> This was my previous understanding of ON_ERROR_STOP. Somewhere in the
> course of developing this patch I lost that. Glad to have it back.
>
> The only changes I made were to invalid booleans on if/elif, and the final
> branch balancing check won't set status to EXIT_USER unless it's
> non-interactive and ON_ERROR_STOP = on.

About v10: Patch applies, make check ok, psql tap test ok. Html doc 
generation ok.

Everything looks ok to me.

Interactive tests behave as expected, especially ctrl-C and with 
on_error_stop=1.

ISTM that everything has been addressed.

I've switched the patch to "Ready for Committers", let's what happens on 
their side...

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 19:58  Robert Haas <robertmhaas@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Robert Haas @ 2017-02-09 19:58 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 6, 2017 at 5:49 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
> I suppressed the endif-balance checking in cases where we're in an
> already-failed situation.
> In cases where there was a boolean parsing failure, and ON_ERROR_STOP is on,
> the error message no longer speak of a future which the session does not
> have. I could left the ParseVariableBool() message as the only one, but
> wasn't sure that that was enough of an error message on its own.
> Added the test case to the existing tap tests. Incidentally, the tap tests
> aren't presently fooled into thinking they're interactive.
>
> $ cat test2.sql
> \if error
>     \echo NO
> \endif
> \echo NOPE
> $ psql test < test2.sql -v ON_ERROR_STOP=0
> unrecognized value "error" for "\if <expr>": boolean expected
> new \if is invalid, ignoring commands until next \endif
> NOPE
> $ psql test < test2.sql -v ON_ERROR_STOP=1
> unrecognized value "error" for "\if <expr>": boolean expected
> new \if is invalid.

I (still) think this is a bad design.  Even if you've got all the
messages just right as things stand today, some new feature that comes
along in the future can change things so that they're not right any
more, and nobody's going to relish maintaining this.  This sort of
thing seems fine to me:

new \if is invalid

But then further breaking it down by things like whether
ON_ERROR_STOP=1 is set, or breaking down the \endif output depending
on the surrounding context, seems terrifyingly complex to me.

Mind you, I'm not planning to commit this patch anyway, so feel free
to ignore me, but if I were planning to commit it, I would not commit
it with that level of chattiness.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 20:13  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Robert Haas <robertmhaas@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-09 20:13 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Robert Haas <robertmhaas@gmail.com> writes:
> I (still) think this is a bad design.  Even if you've got all the
> messages just right as things stand today, some new feature that comes
> along in the future can change things so that they're not right any
> more, and nobody's going to relish maintaining this.

FWIW, I tend to agree that this is way overboard in terms of the amount of
complexity going into the messages.  Also, I do not like what seems to
be happening here:

>> $ psql test < test2.sql -v ON_ERROR_STOP=0
>> unrecognized value "error" for "\if <expr>": boolean expected
>> new \if is invalid, ignoring commands until next \endif

IMO, an erroneous backslash command should have no effect, period.
"It failed but we'll treat it as if it were valid" is a rathole
I don't want to descend into.  It's particularly bad in interactive
mode, because the most natural thing to do is correct your spelling
and issue the command again --- but if psql already decided to do
something on the strength of the mistaken command, that doesn't work,
and you'll have to do something or other to unwind the unwanted
control state before you can get back to what you meant to do.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 21:03  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-09 21:03 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Thu, Feb 9, 2017 at 3:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Robert Haas <robertmhaas@gmail.com> writes:
> > I (still) think this is a bad design.  Even if you've got all the
> > messages just right as things stand today, some new feature that comes
> > along in the future can change things so that they're not right any
> > more, and nobody's going to relish maintaining this.
>
> FWIW, I tend to agree that this is way overboard in terms of the amount of
> complexity going into the messages.  Also, I do not like what seems to
> be happening here:
>
> >> $ psql test < test2.sql -v ON_ERROR_STOP=0
> >> unrecognized value "error" for "\if <expr>": boolean expected
> >> new \if is invalid, ignoring commands until next \endif
>
> IMO, an erroneous backslash command should have no effect, period.
> "It failed but we'll treat it as if it were valid" is a rathole
> I don't want to descend into.  It's particularly bad in interactive
> mode, because the most natural thing to do is correct your spelling
> and issue the command again --- but if psql already decided to do
> something on the strength of the mistaken command, that doesn't work,
> and you'll have to do something or other to unwind the unwanted
> control state before you can get back to what you meant to do.
>
>                         regards, tom lane
>

One way around this is to make the small change: commands with invalid
expressions are ignored in interactive mode.

Another way around it would be to ignore branching commands in interactive
mode altogether and give a message like "branching commands not supported
in interactive mode". That'd get rid of a lot of complexity right there. I
for one wouldn't miss it. The only use I saw for it was debugging a script,
and in that case the user can be their own branching via selective
copy/paste.

Do either of those sound appealing?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 21:15  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-09 21:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> On Thu, Feb 9, 2017 at 3:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> IMO, an erroneous backslash command should have no effect, period.

> One way around this is to make the small change: commands with invalid
> expressions are ignored in interactive mode.

> Another way around it would be to ignore branching commands in interactive
> mode altogether and give a message like "branching commands not supported
> in interactive mode".

Uh, neither of those seem to be responding to my point.  There is no case
in psql where a command with an invalid argument does something beyond
throwing an error.  I do not think that \if is the place to start.

Having it act differently in interactive and noninteractive modes is an
even worse idea.  AFAICS, the only real value of using \if interactively
is to test out something you are about to copy into a script.  If we go
that route we're destroying the ability to test that way.

Basically, I think you need to start removing complexity (in the sense of
special cases), not adding more.  I think Robert was saying the same
thing, though possibly I shouldn't put words in his mouth.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 21:38  Robert Haas <robertmhaas@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Robert Haas @ 2017-02-09 21:38 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Thu, Feb 9, 2017 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Basically, I think you need to start removing complexity (in the sense of
> special cases), not adding more.  I think Robert was saying the same
> thing, though possibly I shouldn't put words in his mouth.

Yeah, I was definitely going in that direction, whatever the details.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-09 21:43  Erik Rijkers <er@xs4all.nl>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Erik Rijkers @ 2017-02-09 21:43 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2017-02-09 22:15, Tom Lane wrote:
> Corey Huinker <corey.huinker@gmail.com> writes:

The feature now  ( at patch v10) lets you break off with Ctrl-C 
anywhere.  I like it now much more.

The main thing I still dislike somewhat about the patch is the verbose 
output. To be honest I would prefer to just remove /all/ the interactive 
output.

I would vote to just make it remain silent if there is no error.   (and 
if there is an error, issue a message and exit)

thanks,

Erik Rijkers


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-10 05:13  Corey Huinker <corey.huinker@gmail.com>
  parent: Erik Rijkers <er@xs4all.nl>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-10 05:13 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Thu, Feb 9, 2017 at 4:43 PM, Erik Rijkers <er@xs4all.nl> wrote:

> On 2017-02-09 22:15, Tom Lane wrote:
>
>> Corey Huinker <corey.huinker@gmail.com> writes:
>>
>
> The feature now  ( at patch v10) lets you break off with Ctrl-C anywhere.
> I like it now much more.
>
> The main thing I still dislike somewhat about the patch is the verbose
> output. To be honest I would prefer to just remove /all/ the interactive
> output.
>
> I would vote to just make it remain silent if there is no error.   (and if
> there is an error, issue a message and exit)
>
> thanks,
>
> Erik Rijkers
>

Changes in this patch:
- invalid boolean expression on \if or \elif is treated as if the script
had a bad \command, so it either stops the script (ON_ERROR_STOP, script
mode), or just gives the ParseVariableBool error and continues.

- All interactive "barks" removed except for
    "command ignored. use \endif or Ctrl-C to exit current branch" when the
user types a non-branching \command in a false branch
    "query ignored. use \endif or Ctrl-C to exit current branch" when the
user types a non-branching \command in a false branch
    "\if: escaped" when a user does press Ctrl-C and they escape a branch.

- remaining error messages are tersed:
     \elif: cannot occur after \else
     \elif: no matching \if
     \else: cannot occur after \else
     \else: no matching \if
     \endif: no matching \if
     found EOF before closing \endif(s)
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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..aadd3f0
--- /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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', '.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/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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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

Attachments:

  [text/plain] 0001.if_endif.v11.diff (25.5K, ../../CADkLM=fbsnkWUWOKz8JiU5hN-8mKDm7MgsuAq6pHk-22eqJ3xA@mail.gmail.com/3-0001.if_endif.v11.diff)
  download | inline diff:
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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..aadd3f0
--- /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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', '.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/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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-10 07:48  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-10 07:48 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> Changes in this patch:
>
> - invalid boolean expression on \if or \elif is treated as if the script 
> had a bad \command, so it either stops the script (ON_ERROR_STOP, script 
> mode), or just gives the ParseVariableBool error and continues.
>
> - All interactive "barks" removed except for [...]
>
> - remaining error messages are tersed: [...]

Patch applies, make check ok, psql tap test ok.

Yep. At least the code is significantly simpler.

There is a useless space on one end of line in the perl script.

Shouldn't there be some documentation changes to reflect the behavior on 
errors? A precise paragraph about that would be welcome, IMHO.

In particular, I suggest that given the somehow more risky "ignore and 
keep going whatever" behavior after a syntax error on if in a script, 
there should be some advice that on_error_stop should better be activated 
in scripts which use \if.

Given that there is no more barking, then having some prompt indication 
that the code is inside a conditional branch becomes more important, so 
ISTM that there should be some plan to add it.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-10 18:41  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-10 18:41 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> Shouldn't there be some documentation changes to reflect the behavior on
> errors? A precise paragraph about that would be welcome, IMHO.
>

Oddly enough, the documentation I wrote hadn't addressed invalid booleans,
only the error messages did that.

The new behavior certainly warrants a mention, and I'll add that.


> Given that there is no more barking, then having some prompt indication
> that the code is inside a conditional branch becomes more important, so
> ISTM that there should be some plan to add it.


Yeah, prompting just got more important. I see a few ways to go about this:

1. Add a new prompt type, either %T for true (heh, pun) or %Y for
branching. It would print a string of chained 't' (branch is true), 'f'
(branch is false), 'z' (branch already had its true section). The depth
traversal would have a limit, say 3 levels deep, and if the tree goes more
than that deep, then '...' would be printed in the stead of any deeper
values. So the prompt would change through a  session like:

command       prompt is now
-----------   ---------------------------------------
\echo bob     ''   = initial state, no branch going on at all
\if yes       't' = inside a true branch
\if no        'tf' = false inside a true
\endif        't' = back to just the true branch
\if yes       'tt'
\if yes       'ttt'
\if yes       '...ttt' = only show the last 3, but let it be known that
there's at least one more'
\else         '...ttz' = past the point of a true bit of this branch

2. The printing of #1 could be integrated into %R only in PROMPT_READY
cases, either prepended or appended to the !/=/^, possibly separated by a :
3. Like #2, but prepended/appended in all circumstances
4. Keep %T (or %Y), and reflect the state of pset.active_branch within %R,
a single t/f/z
5. Like #4, but also printing the if-stack depth if > 1

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-10 21:36  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-10 21:36 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello,

I'm looking forward to the doc update.

My 0.02€ about prompting:

>> Given that there is no more barking, then having some prompt indication
>> that the code is inside a conditional branch becomes more important, so
>> ISTM that there should be some plan to add it.
>
> Yeah, prompting just got more important. I see a few ways to go about this:
>
> 1. Add a new prompt type, either %T for true (heh, pun) or %Y for
> branching. It would print a string of chained 't' (branch is true), 'f'
> (branch is false), 'z' (branch already had its true section). The depth
> traversal would have a limit, say 3 levels deep, and if the tree goes more
> than that deep, then '...' would be printed in the stead of any deeper
> values. So the prompt would change through a  session like:
>
> command       prompt is now
> -----------   ---------------------------------------
> \echo bob     ''   = initial state, no branch going on at all
> \if yes       't' = inside a true branch
> \if no        'tf' = false inside a true
> \endif        't' = back to just the true branch
> \if yes       'tt'
> \if yes       'ttt'
> \if yes       '...ttt' = only show the last 3, but let it be known that
> there's at least one more'
> \else         '...ttz' = past the point of a true bit of this branch

I like the "tfz" idea. I'm not sure whether the up to 6 characters is a 
good, though.

> 2. The printing of #1 could be integrated into %R only in PROMPT_READY
> cases, either prepended or appended to the !/=/^, possibly separated by a :

Hmmm. Logically I would say prepend, but the default prompt is with the 
dbname, which is mostly letters, so it means adding a separator as well.

> 3. Like #2, but prepended/appended in all circumstances

I would say yes.

> 4. Keep %T (or %Y), and reflect the state of pset.active_branch within %R,
> a single t/f/z

Yep, but with a separator?

> 5. Like #4, but also printing the if-stack depth if > 1

Hmmm, not sure...

Based on the your ideas above, I would suggest the following:

   calvin=> \if true
   calvin?t=> SELECT 1 +
   calvin?t->   2;
     3
   calvin?t=> \if true
   calvin?t=>   \echo hello
     hello
   calvin?t=> \endif
   calvin?t=> \else
   calvin?z=>   \echo ignored
   calvin?t=> \endif
   calvin=>

Or maybe use "?.t" for the nested if...

-- 
Fabien
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-10 22:09  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-10 22:09 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>   calvin=> \if true
>
  calvin?t=> SELECT 1 +
>   calvin?t->   2;
>     3
>   calvin?t=> \if true
>   calvin?t=>   \echo hello
>     hello
>   calvin?t=> \endif
>   calvin?t=> \else
>   calvin?z=>   \echo ignored
>   calvin?t=> \endif
>   calvin=>
>

Ok, so that's not just PROMPT_READY, that's every prompt...which might be
ok. ? is a great optional cue, and you're thinking on 2 levels deep, 2nd
level always being '.'? I'll give that a shot.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 07:43  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-11 07:43 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Ok, so that's not just PROMPT_READY, that's every prompt...which might be
> ok. ? is a great optional cue, and you're thinking on 2 levels deep, 2nd
> level always being '.'?

Yep. The idea is to keep it short, but to still have something to say 
"there are more levels" in the stack, hence the one dot. Basically I just 
compressed your 4 level proposal, and added a separator to deal with the 
preceding stuff and suggest the conditional.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 20:07  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-11 20:07 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Feb 11, 2017 at 2:43 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Ok, so that's not just PROMPT_READY, that's every prompt...which might be
>> ok. ? is a great optional cue, and you're thinking on 2 levels deep, 2nd
>> level always being '.'?
>>
>
> Yep. The idea is to keep it short, but to still have something to say
> "there are more levels" in the stack, hence the one dot. Basically I just
> compressed your 4 level proposal, and added a separator to deal with the
> preceding stuff and suggest the conditional.
>
> --
> Fabien.
>

Just realized that '?' means "unknown transactional status" in %x. That
might cause confusion if a person had a prompt of %x%R. Is that enough
reason to pick a different cue?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 20:48  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-11 20:48 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Just realized that '?' means "unknown transactional status" in %x. That
> might cause confusion if a person had a prompt of %x%R. Is that enough
> reason to pick a different cue?

Argh.

"\?\.?[tfz]" seems distinctive enough. Note that %R uses "'=-*^!$( and %x 
uses *!?, which means that they already share 2 characters, so adding ? 
does not seem like a big issue if it was not one before.

Otherwise, maybe "&" or "%", but it is less about a condition.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 22:08  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-11 22:08 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Feb 11, 2017 at 3:48 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Just realized that '?' means "unknown transactional status" in %x. That
>> might cause confusion if a person had a prompt of %x%R. Is that enough
>> reason to pick a different cue?
>>
>
> Argh.
>
> "\?\.?[tfz]" seems distinctive enough. Note that %R uses "'=-*^!$( and %x
> uses *!?, which means that they already share 2 characters, so adding ?
> does not seem like a big issue if it was not one before.
>
> Otherwise, maybe "&" or "%", but it is less about a condition.


Fair enough, it shouldn't be too confusing then.

The get_prompt() function can see the global pset, obviously, but can't see
the scan_state, where the if-stack currently resides. I could give up on
the notion of a per-file if-stack and just have one in pset, but that might
make life difficult for whomever is brave enough to tackle \while loops. Or
I could give pset a pointer to the current if-stack inside the scan_state,
or I could have pset hold a stack of stacks. Unsure which way would be
best.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 22:57  Greg Stark <stark@mit.edu>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Greg Stark @ 2017-02-11 22:57 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 10 February 2017 at 21:36, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
>> command       prompt is now
>> -----------   ---------------------------------------
>> \echo bob     ''   = initial state, no branch going on at all
>> \if yes       't' = inside a true branch
>> \if no        'tf' = false inside a true
>> \endif        't' = back to just the true branch
>> \if yes       'tt'
>> \if yes       'ttt'
>> \if yes       '...ttt' = only show the last 3, but let it be known that
>> there's at least one more'
>> \else         '...ttz' = past the point of a true bit of this branch
>
>
> I like the "tfz" idea. I'm not sure whether the up to 6 characters is a
> good, though.


I haven't been following this thread but just skimming through it for
the first time I thought this was more baroque than I was expecting. I
was expecting something like a { for each level of nested if you're in
so you can see how many deep you are. I didn't expect to see anything
more complex than that.

-- 
greg


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-11 23:45  Corey Huinker <corey.huinker@gmail.com>
  parent: Greg Stark <stark@mit.edu>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-11 23:45 UTC (permalink / raw)
  To: Greg Stark <stark@mit.edu>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Feb 11, 2017 at 5:57 PM, Greg Stark <stark@mit.edu> wrote:

> On 10 February 2017 at 21:36, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
> >> command       prompt is now
> >> -----------   ---------------------------------------
> >> \echo bob     ''   = initial state, no branch going on at all
> >> \if yes       't' = inside a true branch
> >> \if no        'tf' = false inside a true
> >> \endif        't' = back to just the true branch
> >> \if yes       'tt'
> >> \if yes       'ttt'
> >> \if yes       '...ttt' = only show the last 3, but let it be known that
> >> there's at least one more'
> >> \else         '...ttz' = past the point of a true bit of this branch
> >
> >
> > I like the "tfz" idea. I'm not sure whether the up to 6 characters is a
> > good, though.
>
>
> I haven't been following this thread but just skimming through it for
> the first time I thought this was more baroque than I was expecting. I
> was expecting something like a { for each level of nested if you're in
> so you can see how many deep you are. I didn't expect to see anything
> more complex than that.
>

So you'd just want to know nesting depth, with no indicator of true/false?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-12 00:15  Greg Stark <stark@mit.edu>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Greg Stark @ 2017-02-12 00:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 11 February 2017 at 23:45, Corey Huinker <corey.huinker@gmail.com> wrote:
> So you'd just want to know nesting depth, with no indicator of true/false?

Even that's more than bash does, for example:

$ if true ; then
> if false ; then
> :
> fi
> fi

I'm a bit confused how the true/false is actually valuable. It doesn't
tell you how the expression actually evaluated, just where you are in
the code you're typing in which you can tell equally well by looking
at what code you're typing in. The reason nesting level is handy is
just to remind you in case you forget.

For debugging scripts it would be handy to have some way to tell
whether the \if expression actually evaluated to true or false but
that wouldn't be in the prompt I don't think.

-- 
greg


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-12 08:07  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Greg Stark <stark@mit.edu>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-12 08:07 UTC (permalink / raw)
  To: Greg Stark <stark@mit.edu>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Greg,

>> So you'd just want to know nesting depth, with no indicator of true/false?
>
> Even that's more than bash does, for example: [...]

Indeed, there is nothing in "bash" prompt about nesting control 
structures. However other shells have such indications: "zsh" has "%_", 
"tcsh" has "%R". In tcsh for example, there is mention of the structure 
type but none of nesting depth nor truth:

   >    if ( 0 ) then
   if?    ...

> I'm a bit confused how the true/false is actually valuable.

The point is just to tell the user that the next command (1) is under an 
if control structure and (2) whether it is going to be executed or 
ignored. That is not too bad in 2 characters.

> It doesn't tell you how the expression actually evaluated,

I do not get your point... t tells that it was true, f that it was false?

> just where you are in the code you're typing in which you can tell 
> equally well by looking at what code you're typing in.

   SELECT ... AS condition \gset
   \if :condition ...

The value of the condition is not obvious from the code, it depends on the 
database state.

> The reason nesting level is handy is just to remind you in case you 
> forget.

Sure, that can be useful too.

> For debugging scripts it would be handy to have some way to tell
> whether the \if expression actually evaluated to true or false but
> that wouldn't be in the prompt I don't think.

Are you suggest to add another command to display the current stack state, 
eg "\ifstate" or whatever?

"\if" is really about scripting, so the idea was to have something quite 
light for interactive debugging, especially to help the user not to be 
stuck into a false branch, hence the prompt information with t/f/z.

What should be in the prompt is indeed debatable: existence, nesting 
depth, current truth value, part of the stack... I think that something, 
whatever it is, is necessary.

Maybe this can be a discussed in a follow-up patch and Corey should 
proceed to finalize the if patch?

-- 
Fabien


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 07:55  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 07:55 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> Maybe this can be a discussed in a follow-up patch and Corey should
> proceed to finalize the if patch?


In the event that we can leave prompting to a later patch, here are the v12
highlights:
- created conditional.h and conditional.c which contain the functions with
stack-ish push/pop/peek/poke names
- now all non-test, non-doc changes are in src/bin/psql
- moved conditional stack out of scan_state, stack state maintained by
mainloop.c/startup.c, passed to HandleSlashCommands
- documentation encourages the user to employ ON_ERROR_STOP when using
conditionals
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..bdef6e9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,77 @@ 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>
+        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 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..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..fded2bc 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 && pset.active_branch)
 	{
 		/* 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 (!pset.active_branch && !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,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				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);
+				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 && elif_true)
+				{
+					conditional_stack_poke(cstack, IFSTATE_TRUE);
+					pset.active_branch = 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);
+				pset.active_branch = 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
+				 */
+				pset.active_branch = false;
+				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)
+			pset.active_branch = conditional_active(cstack);
+		else
+			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..698ca1f 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..1875329
--- /dev/null
+++ b/src/bin/psql/conditional.c
@@ -0,0 +1,107 @@
+/*
+ * 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 (cstack->head != NULL)
+	{
+		IfStackElem *p = cstack->head;
+		cstack->head = cstack->head->next;
+		free(p);
+	}
+	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/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..240357c 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -10,12 +10,13 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "input.h"
 #include "prompt.h"
 #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 +24,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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -50,6 +67,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 +87,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 +141,20 @@ 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);
+					pset.active_branch =
+						conditional_active(cond_stack);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +328,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;
 
@@ -343,6 +375,7 @@ MainLoop(FILE *source)
 
 				/* execute backslash command */
 				slashCmdStatus = HandleSlashCmds(scan_state,
+												 cond_stack,
 												 query_buf->len > 0 ?
 												 query_buf : previous_buf);
 
@@ -358,7 +391,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 +400,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 +463,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,7 +484,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/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..a67c89f 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;
@@ -335,19 +337,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..15dbca1
--- /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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', '.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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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

Attachments:

  [text/plain] 0001.if_endif.v12.diff (29.7K, ../../CADkLM=fj9EOqCB89nJ7Qm9OKdrF9xL0qqaRq3VFa5=vO4faUzg@mail.gmail.com/3-0001.if_endif.v12.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..bdef6e9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,77 @@ 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>
+        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 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..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..fded2bc 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 && pset.active_branch)
 	{
 		/* 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 (!pset.active_branch && !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,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				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);
+				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 && elif_true)
+				{
+					conditional_stack_poke(cstack, IFSTATE_TRUE);
+					pset.active_branch = 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);
+				pset.active_branch = 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
+				 */
+				pset.active_branch = false;
+				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)
+			pset.active_branch = conditional_active(cstack);
+		else
+			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..698ca1f 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..1875329
--- /dev/null
+++ b/src/bin/psql/conditional.c
@@ -0,0 +1,107 @@
+/*
+ * 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 (cstack->head != NULL)
+	{
+		IfStackElem *p = cstack->head;
+		cstack->head = cstack->head->next;
+		free(p);
+	}
+	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/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..240357c 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -10,12 +10,13 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "input.h"
 #include "prompt.h"
 #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 +24,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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -50,6 +67,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 +87,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 +141,20 @@ 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);
+					pset.active_branch =
+						conditional_active(cond_stack);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +328,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;
 
@@ -343,6 +375,7 @@ MainLoop(FILE *source)
 
 				/* execute backslash command */
 				slashCmdStatus = HandleSlashCmds(scan_state,
+												 cond_stack,
 												 query_buf->len > 0 ?
 												 query_buf : previous_buf);
 
@@ -358,7 +391,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 +400,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 +463,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,7 +484,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/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..a67c89f 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;
@@ -335,19 +337,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..15dbca1
--- /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', 'syntax error' ],
+	[ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean expected', 'syntax error' ],
+	# unmatched checks
+	[ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' ],
+	[ "\\elif true\n\\echo NO\n", '', '.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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 12:01  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-13 12:01 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org



>> Maybe this can be a discussed in a follow-up patch and Corey should
>> proceed to finalize the if patch?
>
> In the event that we can leave prompting to a later patch, here are the v12
> highlights:

My 0.02€ about v12: Patch applies, make check ok, psql make check ok.

> - created conditional.h and conditional.c which contain the functions with
> stack-ish push/pop/peek/poke names

Why not.

> - now all non-test, non-doc changes are in src/bin/psql

Hmmm, see below.

> - moved conditional stack out of scan_state, stack state maintained by
> mainloop.c/startup.c, passed to HandleSlashCommands

ISTM that it is kind of a regression, because logically this is about the 
scan state so it should be in the corresponding structure, and having two 
structures to pass the scan state is not an improvement...

> - documentation encourages the user to employ ON_ERROR_STOP when using
> conditionals

Indeed. The paragraph explanations are clear enough to me.

I would suggest to also apply the advice to the example shown, including a 
comment about why the variable is set on.

Also, the last element of the tap tests should be distinct: I suggest to 
use 'if syntax error' and 'elif syntax error' in place of 'syntax error' 
for the two first tests.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 16:26  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 16:26 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> ISTM that it is kind of a regression, because logically this is about the
> scan state so it should be in the corresponding structure, and having two
> structures to pass the scan state is not an improvement...


I wasn't too happy with the extra param, either. Having moved the guts to
conditional.c, I'm happy with that change and can move the stack head back
to scan_state with a clear conscience.


> I would suggest to also apply the advice to the example shown, including a
> comment about why the variable is set on.
>

+1

>
> Also, the last element of the tap tests should be distinct: I suggest to
> use 'if syntax error' and 'elif syntax error' in place of 'syntax error'
> for the two first tests.


+1, shouldn't take too long.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 16:29  Robert Haas <robertmhaas@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 2 replies; 232+ messages in thread

From: Robert Haas @ 2017-02-13 16:29 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Feb 11, 2017 at 2:43 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
>> Ok, so that's not just PROMPT_READY, that's every prompt...which might be
>> ok. ? is a great optional cue, and you're thinking on 2 levels deep, 2nd
>> level always being '.'?
>
> Yep. The idea is to keep it short, but to still have something to say "there
> are more levels" in the stack, hence the one dot. Basically I just
> compressed your 4 level proposal, and added a separator to deal with the
> preceding stuff and suggest the conditional.

I think we should try to make this REALLY simple.  We don't really
want to have everybody have to change their PROMPT1 and PROMPT2
strings for this one feature. How about just introducing a new value
for %R?  The documentation currently says:

In prompt 1 normally =, but ^ if in single-line mode, or ! if the
session is disconnected from the database (which can happen if
\connect fails).

...and suppose we just extend that to add:

, or @ if commands are currently being ignored because of the result
of an \if test.

The latter would include being in the \if section when the conditional
was true as well as being in the \else section when the conditional
was false.  I think that's all you need here: a way to alert users as
to whether commands are being ignored, or not.  Putting details in
about precisely why they are being ignored seems like it's too
complicated; people won't remember how to decode some bizarre series
of glyphs that we output.  Telling them whether their next command is
set to be ignored or executed is good enough; if the answer isn't what
they expect, they can debug their script to figure out what they
screwed up.

Also, keep in mind that people don't need to know everything from the
current prompt. They can try to debug things by looking back at
previous prompts. They'll understand that \if is going to introduce a
new nesting level and \endif is going to end one, and that \else and
\elseif may change things.  Aside from keeping the code simple so we
can maintain it and the output simple so that users can remember what
it means, I just don't believe that it's really going to be helpful to
convey much detail here.   People aren't going to paste in a gigaton
of commands and then look only at the last line of the output and try
to understand what it's telling them, or if they do that and are
confused, I think nobody will really feel bad about giving them the
advice "scroll up" or "try a simpler test case first".

Further keep in mind that eventually somebody's going to code \while
or \for or something, and then there are going to be even more
possible states here.  Just when you've figured out what tfzffft
means, they'll be the case of a \while loop which is getting skipped
because the condition at the top turned out to be false on the first
iteration, or where (half-joking) we're skipping commands until we
find the label that matches an executed \goto.  Writing maintainable
code includes leaving room open for other people to do stuff we can't
even foresee today, and that means we need not to use up a
disproportionate number of the glyphs that can reasonably be used in a
psql prompt just on this.  This is one small feature out of many that
psql has, and one small hint to the user about whether it's currently
causing commands to be skipped seems sufficient.

All IMHO, of course.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 17:47  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 17:47 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 13, 2017 at 11:26 AM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> ISTM that it is kind of a regression, because logically this is about the
>> scan state so it should be in the corresponding structure, and having two
>> structures to pass the scan state is not an improvement...
>
>
> I wasn't too happy with the extra param, either. Having moved the guts to
> conditional.c, I'm happy with that change and can move the stack head back
> to scan_state with a clear conscience.
>

So moving the conditional stack back into PsqlScanState has some side
effects: conditional.[ch] have to move to the fe_utils/ dirs, and now
pgbench, which does not use conditionals, would have to link to them. Is
that a small price to pay for modularity and easier-to-find code? Or should
I just tuck it back into psqlscan_int.[ch]?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 19:29  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 19:29 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> I wasn't too happy with the extra param, either. Having moved the guts to
>> conditional.c, I'm happy with that change and can move the stack head back
>> to scan_state with a clear conscience.
>>
>
>
That effort was creating as many headaches as it solved. Rolled back v12
except for doc paragraph, added more descriptive tap test names. Enjoy.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..e86b66b 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+-- set ON_ERROR_STOP in case the variables are not valid boolean expressions
+\set ON_ERROR_STOP on
+\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>
+        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>
+        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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..53a0b29 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;
 }
 
@@ -913,6 +915,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 void
 psql_scan_destroy(PsqlScanState state)
 {
+	while (psqlscan_branch_pop(state))
+		continue;
+
 	psql_scan_finish(state);
 
 	psql_scan_reset(state);
@@ -1426,3 +1431,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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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

Attachments:

  [text/plain] 0001.if_endif.v13.diff (24.7K, ../../CADkLM=dHu+6WGFfnTipbrR2=faHrO1o4wSeZpOVO_YACbonuZA@mail.gmail.com/3-0001.if_endif.v13.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..e86b66b 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+-- set ON_ERROR_STOP in case the variables are not valid boolean expressions
+\set ON_ERROR_STOP on
+\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>
+        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>
+        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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..53a0b29 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;
 }
 
@@ -913,6 +915,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 void
 psql_scan_destroy(PsqlScanState state)
 {
+	while (psqlscan_branch_pop(state))
+		continue;
+
 	psql_scan_finish(state);
 
 	psql_scan_reset(state);
@@ -1426,3 +1431,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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 20:04  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-13 20:04 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> That effort was creating as many headaches as it solved. Rolled back v12 
> except for doc paragraph, added more descriptive tap test names. Enjoy.

The TAP tests are missing from the patch, not sure why...

The stack cleanup is nicer with pop calls.

Two debatable suggestions about the example:
  - put the on_error_stop as the very first thing in the example?
  - add a comment explaining what the SELECT ... \gset does?

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 20:18  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 20:18 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 13, 2017 at 3:04 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> That effort was creating as many headaches as it solved. Rolled back v12
>> except for doc paragraph, added more descriptive tap test names. Enjoy.
>>
>
> The TAP tests are missing from the patch, not sure why...
>

I went back to master and re-applied v11, something must have gotten lost
in translation.


> The stack cleanup is nicer with pop calls.
>

Yeah, dunno why I didn't do that earlier.


> Two debatable suggestions about the example:
>  - put the on_error_stop as the very first thing in the example?
>  - add a comment explaining what the SELECT ... \gset does?


Both are reasonable and easy. Added.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..7e59192 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>
+-- stop the script if the condition variables are invalid 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>
+        <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>
+        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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..53a0b29 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;
 }
 
@@ -913,6 +915,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 void
 psql_scan_destroy(PsqlScanState state)
 {
+	while (psqlscan_branch_pop(state))
+		continue;
+
 	psql_scan_finish(state);
 
 	psql_scan_reset(state);
@@ -1426,3 +1431,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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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

Attachments:

  [text/plain] 0001.if_endif.v14.diff (26.3K, ../../CADkLM=dKXVHn2pCx-JZqbsXb-fT7nn5LLebxTkmd3XPe4ayBdg@mail.gmail.com/3-0001.if_endif.v14.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..7e59192 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>
+-- stop the script if the condition variables are invalid 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>
+        <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>
+        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..390bccd 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,18 @@ 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("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 +1043,154 @@ 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 (pset.active_branch)
+		{
+			bool if_true = false;
+			success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+			if (success)
+			{
+				pset.active_branch = if_true;
+				psqlscan_branch_push(scan_state,
+					(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 (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 && elif_true)
+				{
+					psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+					pset.active_branch = 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 (psqlscan_branch_get_state(scan_state))
+		{
+			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_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
+				 */
+				pset.active_branch = false;
+				psqlscan_branch_set_state(scan_state, 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 = psqlscan_branch_pop(scan_state);
+		if (success)
+			pset.active_branch = psqlscan_branch_active(scan_state);
+		else
+			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/common.c b/src/bin/psql/common.c
index 5349c39..698ca1f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident)
 	char	   *result;
 	const char *value;
 
+	/* do not expand variables if the branch is inactive */
+	if (!pset.active_branch)
+		return NULL;
+
 	value = GetVariable(pset.vars, varname);
 	if (!value)
 		return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
 	fprintf(output, "\n");
 
+	fprintf(output, _("Conditionals\n"));
+	fprintf(output, _("  \\if <expr>             begin a conditional block\n"));
+	fprintf(output, _("  \\elif <expr>           else if in the current conditional block\n"));
+	fprintf(output, _("  \\else                  else in the current conditional block\n"));
+	fprintf(output, _("  \\endif                 end current conditional block\n"));
+	fprintf(output, "\n");
+
 	fprintf(output, _("Informational\n"));
 	fprintf(output, _("  (options: S = show system objects, + = additional detail)\n"));
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..82fbf20 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("query ignored, use \\endif or Ctrl-C to exit current branch.\n");
+
+	return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +138,19 @@ MainLoop(FILE *source)
 			cancel_pressed = false;
 
 			if (pset.cur_cmd_interactive)
+			{
 				putc('\n', stdout);
+				/*
+				 * if interactive user is in a branch, then Ctrl-C will exit
+				 * from the inner-most branch
+				 */
+				if (!psqlscan_branch_stack_empty(scan_state))
+				{
+					psql_error("\\if: escaped\n");
+					psqlscan_branch_pop(scan_state);
+					pset.active_branch = psqlscan_branch_active(scan_state);
+				}
+			}
 			else
 			{
 				successResult = EXIT_USER;
@@ -296,8 +324,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 +386,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 +395,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 +458,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 +479,19 @@ 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
+		&& !psqlscan_branch_stack_empty(scan_state))
+	{
+		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);
 
 	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..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/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..53a0b29 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;
 }
 
@@ -913,6 +915,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 void
 psql_scan_destroy(PsqlScanState state)
 {
+	while (psqlscan_branch_pop(state))
+		continue;
+
 	psql_scan_finish(state);
 
 	psql_scan_reset(state);
@@ -1426,3 +1431,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..22b8102 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,99 @@ 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 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
+\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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..c37e744 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
+
+-- 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 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 20:40  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Robert Haas <robertmhaas@gmail.com>
  1 sibling, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-13 20:40 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Robert,

> [...] I think we should try to make this REALLY simple.  We don't really 
> want to have everybody have to change their PROMPT1 and PROMPT2 strings 
> for this one feature.

Ok. I think that we agree that the stack was too much details.

> How about just introducing a new value for %R?

Yes. That is indeed one of the idea being discussed.

> [...] , or @ if commands are currently being ignored because of the 
> result of an \if test.

Currently I find that %R logic is quite good, with "=" for give me 
something, "^" is start line regular expression for one line, "!" for 
beware someting is amiss, and in prompt2 "-" for continuation, '"' for in 
double quotes, "(" for in parenthesis and so on.

What would be the mnemonic for "," an "@"?

By shortening one of the suggestion down to two characters, we may have 
three cases:

   "?t" for "in condition, in true block"
   "?f" for "in condition, in false block (but true yet to come)"
   "?z" for "in condition, waiting for the end (true has been executed)".

So no indication about the stack depth and contents. tfz for true false 
and sleeping seem quite easy to infer and understand. "?" is also needed 
as a separator with the previous field which is the database name 
sometimes:

   calvin=> \if false
   calvin?f=> \echo 1
   calvin?f=> \elif true
   calvin?t=> \echo 2
     2
   calvin?t=> \else
   calvin?z=> \echo 3
   calvin?z=> \endif
   calvin=>

With the suggested , and @:

   calvin=> \if false
   calvin,=> \echo 1
   calvin,=> \elif true
   calvin@=> \echo 2
     2
   calvin@=> \else
   calvin,=> \echo 3
   calvin,=> \endif
   calvin=>

If I can find some simple mnemonic for "," vs "@" for being executed vs 
ignored, I could live with that, but nothing obvious comes to my mind.

The "?" for condition and Corey's [tfz] looked quite intuitive/mnemonic to 
me. The drawback is that it is 2 chars vs one char in above.

> [...] I think that's all you need here: a way to alert users as to 
> whether commands are being ignored, or not.

Yep.

> [...]

To sum up your points: just update %R (ok), keep it simple/short (ok... 
but how simple [2 vs 3 states] and short [1 or 2 chars]), and no real need 
to be too nice with the user beyond the vital (ok, that significantly 
simplifies things).

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 21:05  Corey Huinker <corey.huinker@gmail.com>
  parent: Robert Haas <robertmhaas@gmail.com>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 21:05 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 13, 2017 at 11:29 AM, Robert Haas <robertmhaas@gmail.com> wrote:

> possible states here.  Just when you've figured out what tfzffft


I agree with what you've said, but wanted to point out that any condition
that follows a 'z' would itself be 'z'. Not that tfzzzzz is much better.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 21:06  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-13 21:06 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> I went back to master and re-applied v11, something must have gotten lost
> in translation.

Probably you need "git add" for added files?

About v14: patch applies, make check ok, psql tap tests ok.

All seems fine to me. Test coverage is better than a lot of other 
features. Code & comments seem fine. Doc and example are clear enough to 
me.

The level of messages in interactive is terse but informative when needed, 
on errors and when commands are ignored. The only missing point is about 
doing something to the prompt, but given the current messages ISTM that 
this can wait for a follow-up patch. Robert Haas advice is to keep it 
simple and short and in %R. There was also some suggestion to have a "show 
the stack" command for debug, I think that this can wait as well.

I've turned again the CF entry to "ready for committers", to see what 
committers thing about this new and simplified version.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-13 21:12  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-13 21:12 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Robert Haas <robertmhaas@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 13, 2017 at 3:40 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Robert,
>
> [...] I think we should try to make this REALLY simple.  We don't really
>> want to have everybody have to change their PROMPT1 and PROMPT2 strings for
>> this one feature.
>>
>
> Ok. I think that we agree that the stack was too much details.
>
> How about just introducing a new value for %R?
>>
>
> Yes. That is indeed one of the idea being discussed.
>
> [...] , or @ if commands are currently being ignored because of the result
>> of an \if test.
>>
>
,-or-@ has one advantage over t/f/z: we cannot infer the 'z' state purely
from pset.active_state, and the if-stack itself is sequestered in
scan_state, which is not visible to the get_prompt() function.

I suppose if somebody wanted it, a separate slash command that does a
verbose printing of the current if-stack would be nice, but mostly just to
explain to people how the if-stack works.


> If I can find some simple mnemonic for "," vs "@" for being executed vs
> ignored, I could live with that, but nothing obvious comes to my mind.
>

@in't gonna execute it?

I'm here all week, try the veal.

To sum up your points: just update %R (ok), keep it simple/short (ok... but
> how simple [2 vs 3 states] and short [1 or 2 chars]), and no real need to
> be too nice with the user beyond the vital (ok, that significantly
> simplifies things).


I'd be fine with either of these on aesthetic grounds. On technical
grounds, 'z' is harder to show.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 07:40  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-14 07:40 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

>> If I can find some simple mnemonic for "," vs "@" for being executed vs
>> ignored, I could live with that, but nothing obvious comes to my mind.
>
> @in't gonna execute it?

Hmmm... This is too much of an Americanism, IMHO.

> I'm here all week, try the veal.

Sorry, syntax error, you have lost me. Some googling suggests a reference 
to post WW2 "lounge entertainers", probably in the USA. I also do not 
understand why this would mean "yes".

> I'd be fine with either of these on aesthetic grounds. On technical
> grounds, 'z' is harder to show.

I'm not sure that this valid technical point should be a good reason for 
guiding what feedback should be provided to the user, but it makes it 
simpler to choose two states:-)

For three states with more culturally neutral mnemonics, I thought of:
   ? for f (waiting for a true answer...)
   . for z (waiting for the end of the sentence, i.e. endif)
   & for t (no real mnemonic)

For two states:
   * for being executed (beware, it is ***important***)
   / for not (under the hood, and it is opposed to *)

Otherwise I still like "?[tfz]", but it is two characters long.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 09:33  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-14 09:33 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Robert Haas <robertmhaas@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> @in't gonna execute it?
>>
>
> Hmmm... This is too much of an Americanism, IMHO.


The @ looks like a handwritten 'a'.  @in't gonna => ain't gonna => will
not. It's a bad joke, made as a way of saying that I also could not think
of a good mnemonic for '@' or ','.


> I'm here all week, try the veal.
>>
>
> Sorry, syntax error, you have lost me. Some googling suggests a reference
> to post WW2 "lounge entertainers", probably in the USA. I also do not
> understand why this would mean "yes".


It's a thing lounge entertainers said after they told a bad joke.


>   . for z (waiting for the end of the sentence, i.e. endif)
>

+1 ... if we end up displaying the not-true-and-not-evaluated 'z' state.


>   & for t (no real mnemonic)
>
> For two states:
>   * for being executed (beware, it is ***important***)
>

It does lend importance, but that's also the line continuation marker for
"comment". Would that be a problem?


>   / for not (under the hood, and it is opposed to *)
>

+1, I was going to suggest '/' for a false state, with two possible
metaphors to justify it
  1. the slash in a "no" sign ("no smoking", ghostbusters, etc)
  2. the leading char of a c/java/javascript comment (what is written here
is just words, not code)

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 10:22  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-14 10:22 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


>> For two states:
>>   * for being executed (beware, it is ***important***)
>
> It does lend importance, but that's also the line continuation marker for
> "comment". Would that be a problem?

Argh. Indeed, even if people seldom type C comments in psql interactive 
mode...

Remaining ASCII characters I can thing of, hopefully avoiding already used 
ones: +%,@$\`|&:;_

So, maybe consider these ones:
   "+" for it is "on"
   "`" which is a "sub-shell execution"
   "&" for "and the next command is ..."

>>   / for not (under the hood, and it is opposed to *)
>
> +1, I was going to suggest '/' for a false state, with two possible
>     metaphors to justify it
>  1. the slash in a "no" sign ("no smoking", ghostbusters, etc)
>  2. the leading char of a c/java/javascript comment (what is written here
>     is just words, not code)

Great.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 18:26  Robert Haas <robertmhaas@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Robert Haas @ 2017-02-14 18:26 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Erik Rijkers <er@xs4all.nl>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Feb 13, 2017 at 3:40 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
> What would be the mnemonic for "," an "@"?

Oh, I just picked it because control-@ is the nul character, and your
commands would be nullified.  I realize that's pretty weak, but we're
talking about finding a punctuation mark to represent the concept of
commands-are-currently-being-skipped, and it doesn't seem particularly
worse than ^ to represent single-line mode.  If somebody's got a
better idea, fine, but there aren't that many unused punctuation marks
to choose from, and I think it's better to use a punctuation mark
rather than, say, a letter, like 's' for skip.  Otherwise you might
have the prompt change from:

banana=>

to

bananas>

Which I think is less obvious than

banana@>

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 21:44  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-14 21:44 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> So moving the conditional stack back into PsqlScanState has some side
> effects: conditional.[ch] have to move to the fe_utils/ dirs, and now
> pgbench, which does not use conditionals, would have to link to them. Is
> that a small price to pay for modularity and easier-to-find code? Or should
> I just tuck it back into psqlscan_int.[ch]?

Pardon me for coming in late, but what in the world has this to do with
the lexer's state at all?  IOW, I don't think I like either of what you're
suggesting ...

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 23:01  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-14 23:01 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Tue, Feb 14, 2017 at 4:44 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Corey Huinker <corey.huinker@gmail.com> writes:
> > So moving the conditional stack back into PsqlScanState has some side
> > effects: conditional.[ch] have to move to the fe_utils/ dirs, and now
> > pgbench, which does not use conditionals, would have to link to them. Is
> > that a small price to pay for modularity and easier-to-find code? Or
> should
> > I just tuck it back into psqlscan_int.[ch]?
>
> Pardon me for coming in late, but what in the world has this to do with
> the lexer's state at all?  IOW, I don't think I like either of what you're
> suggesting ...
>
>                         regards, tom lane
>

Patch v12 has them separated, if that was more to your liking. The stack
state lived in MainLoop() and was passed into HandleSlashCommands with an
extra state variable.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-14 23:03  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-02-14 23:03 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

>> So moving the conditional stack back into PsqlScanState has some side
>> effects: conditional.[ch] have to move to the fe_utils/ dirs, and now
>> pgbench, which does not use conditionals, would have to link to them. Is
>> that a small price to pay for modularity and easier-to-find code? Or should
>> I just tuck it back into psqlscan_int.[ch]?
>
> Pardon me for coming in late, but what in the world has this to do with
> the lexer's state at all?  IOW, I don't think I like either of what you're
> suggesting ...

The "lexer" state holds the stuff useful to psql to know where commands 
start and stop, to process backslash commands, including counting 
parenthesis and nested comments and so on... It seems logical to put the 
"if" stack there as well, but if you think that it should be somewhere 
else, please advise Corey about where to put it.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 15:20  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-02-22 15:20 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>  but if you think that it should be somewhere else, please advise Corey
> about where to put it.


Just a reminder that I'm standing by for advice.

The issue at hand is whether the if-state should be a part of the
PsqlScanState, or if it should be a separate state variable owned by
MainLoop() and passed to HandleSlashCommands(), ... or some other solution.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 21:00  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-02-22 21:00 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
>> but if you think that it should be somewhere else, please advise Corey
>> about where to put it.

> Just a reminder that I'm standing by for advice.

Sorry, I'd lost track of this thread.

> The issue at hand is whether the if-state should be a part of the
> PsqlScanState, or if it should be a separate state variable owned by
> MainLoop() and passed to HandleSlashCommands(), ... or some other solution.

My reaction to putting it in PsqlScanState is pretty much "over my dead
body".  That's just trashing any pretense of an arms-length separation
between psql and the lexer proper.  We only recently got done sweating
blood to create that separation, why would we toss it away already?

If you're concerned about the notational overhead of passing two arguments
rather than one, my druthers would be to invent a new struct type, perhaps
named along the lines of PsqlFileState or PsqlInputState, and pass that
around.  One of its fields would be a PsqlScanState pointer, the rest
would be for if-state and whatever else we think we need in per-input-file
state.

However, that way is doubling down on the assumption that the if-state is
exactly one-to-one with input file levels, isn't it?  We might be better
off to just live with the separate arguments to preserve some flexibility
in that regard.  The v12 patch doesn't look that awful in terms of what
it's adding to argument lists.

One thing I'm wondering is why the "active_branch" bool is in "pset"
and not in the conditional stack.  That seems, at best, pretty grotty.
_psqlSettings is meant for reasonably persistent state.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 21:19  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-22 21:19 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 22, 2017 at 4:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Corey Huinker <corey.huinker@gmail.com> writes:
> >> but if you think that it should be somewhere else, please advise Corey
> >> about where to put it.
>
> > Just a reminder that I'm standing by for advice.
>
> Sorry, I'd lost track of this thread.
>

Judging by the volume of the 50-or-so active threads on this list, I
figured you were busy.


> > The issue at hand is whether the if-state should be a part of the
> > PsqlScanState, or if it should be a separate state variable owned by
> > MainLoop() and passed to HandleSlashCommands(), ... or some other
> solution.
>
> My reaction to putting it in PsqlScanState is pretty much "over my dead
> body".  That's just trashing any pretense of an arms-length separation
> between psql and the lexer proper.  We only recently got done sweating
> blood to create that separation, why would we toss it away already?
>

Good to know that history.


>
> If you're concerned about the notational overhead of passing two arguments
> rather than one, my druthers would be to invent a new struct type, perhaps
> named along the lines of PsqlFileState or PsqlInputState, and pass that
> around.  One of its fields would be a PsqlScanState pointer, the rest
> would be for if-state and whatever else we think we need in per-input-file
> state.
>

I wasn't, my reviewer was. I thought about the super-state structure like
you described, and decided I was happy with two state params.


> However, that way is doubling down on the assumption that the if-state is
> exactly one-to-one with input file levels, isn't it?  We might be better
> off to just live with the separate arguments to preserve some flexibility
> in that regard.  The v12 patch doesn't look that awful in terms of what
> it's adding to argument lists.
>

The rationale for tying if-state to file levels is not so much of anything
if-then related, but rather of the mess we'd create for whatever poor soul
decided to undertake \while loops down the road, and the difficulties
they'd have trying to unwind/rewind jump points in file(s)...keeping it
inside one file makes things simpler for the coding and the coder.


> One thing I'm wondering is why the "active_branch" bool is in "pset"
> and not in the conditional stack.  That seems, at best, pretty grotty.
> _psqlSettings is meant for reasonably persistent state.
>

With the if-stack moved to MainLoop(), nearly all the active_branch checks
could be against a variable that lives in MainLoop(), with two big
exceptions: GetVariable() needs to know when NOT to expand a variable
because it's in a false-block, and get_prompt will need to know when it's
in a false block for printing the '@' prompt hint or equivalent, and pset
is the only global around I know of to do that. I can move nearly all the
is-this-branch-active checks to structures inside of MainLoop(), and that
does strike me as cleaner, but there will still have to be that gross bit
where we update pset.active_branch so that the prompt and GetVariable() are
clued in.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 21:45  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-02-22 21:45 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> On Wed, Feb 22, 2017 at 4:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> One thing I'm wondering is why the "active_branch" bool is in "pset"
>> and not in the conditional stack.  That seems, at best, pretty grotty.
>> _psqlSettings is meant for reasonably persistent state.

> With the if-stack moved to MainLoop(), nearly all the active_branch checks
> could be against a variable that lives in MainLoop(), with two big
> exceptions: GetVariable() needs to know when NOT to expand a variable
> because it's in a false-block, and get_prompt will need to know when it's
> in a false block for printing the '@' prompt hint or equivalent, and pset
> is the only global around I know of to do that.

Dunno, that sounds a lot like an "if the only tool I have is a hammer,
then this must be a nail" argument.  pset should not accrete every single
global variable in psql just because it's there.  Actually, there's a
pretty fair amount of stuff in it already that should not be there by any
reasonable interpretation of what it's for.  Inventing a PsqlFileState or
similar struct might be a good idea to help pull some of that cruft out of
pset and get it back to having a reasonably clearly defined purpose of
holding "current settings".

So I think that if you're intent on this being a global variable, it might
as well be a standalone global variable.  I was wondering more about
whether we shouldn't be passing the condition-stack top pointer around
to places that need to know about conditional execution.  get_prompt would
be one if we decide that the prompt might need to reflect this (a question
that still seems undecided to me --- I think we'd be better off with "this
command was ignored" warning messages).  I'm failing to follow why
GetVariable would need to care.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 22:11  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-22 22:11 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> Dunno, that sounds a lot like an "if the only tool I have is a hammer,
> then this must be a nail" argument.


More of a "don't rock the boat more than absolutely necessary", but knowing
that adding another global struct might be welcomed is good to know.


> reasonable interpretation of what it's for.  Inventing a PsqlFileState or
> similar struct might be a good idea to help pull some of that cruft out of
> pset and get it back to having a reasonably clearly defined purpose of
> holding "current settings".
>

+1

command was ignored" warning messages).  I'm failing to follow why
>
GetVariable would need to care.
>

It took me a second to find the post, written by Daniel Verite on Jan 26,
quoting:

> Revised patch

A comment about control flow and variables:
in branches that are not taken, variables are expanded
nonetheless, in a way that can be surprising.
Case in point:

\set var 'ab''cd'
-- select :var;
\if false
  select :var ;
\else
  select 1;
\endif

The 2nd reference to :var has a quoting hazard, but since it's within
an "\if false" branch, at a glance it seems like this script might work.
In fact it barfs with:
  psql:script.sql:0: found EOF before closing \endif(s)

AFAICS what happens is that :var gets injected and starts a
runaway string, so that as far as the parser is concerned
the \else ..\endif block slips into the untaken branch, as a part of
that unfinished string.



So that was the reasoning behind requiring GetVariable to know whether or
not the statement was being ignored.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 22:52  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-22 22:52 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 22, 2017 at 5:11 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> Dunno, that sounds a lot like an "if the only tool I have is a hammer,
>> then this must be a nail" argument.
>
>
> More of a "don't rock the boat more than absolutely necessary", but
> knowing that adding another global struct might be welcomed is good to know.
>
>

After some research, GetVariable is called by psql_get_variable, which is
one of the callback functions passed to psql_scan_create(). So passing a
state variable around probably isn't going to work and PsqlFileState now
looks like the best option.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 22:59  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-02-22 22:59 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> After some research, GetVariable is called by psql_get_variable, which is
> one of the callback functions passed to psql_scan_create(). So passing a
> state variable around probably isn't going to work and PsqlFileState now
> looks like the best option.

Ah, I see why *that* wants to know about it ... I think.  I suppose you're
arguing that variable expansion shouldn't be able to insert, say, an \else
in a non-active branch?  Maybe, but if it can insert an \else in an active
branch, then why not non-active too?  Seems a bit inconsistent.

Anyway, what this seems to point up is that maybe we should've allowed
for a passthrough "void *" argument to the psqlscan callback functions.
There wasn't one in the original design but it's a fairly standard part
of our usual approach to callback functions, so it's hard to see an
objection to adding one now.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-22 23:15  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-22 23:15 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 22, 2017 at 5:59 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Ah, I see why *that* wants to know about it ... I think.  I suppose you're
> arguing that variable expansion shouldn't be able to insert, say, an \else
> in a non-active branch?  Maybe, but if it can insert an \else in an active
> branch, then why not non-active too?  Seems a bit inconsistent.
>

The major reason was avoiding situations like what Daniel showed: where
value of a variable that is meaningless/undefined in the current
false-block context gets expanded anyway, and thus code inside a false
block has effects outside of that block. Granted, his example was
contrived. I'm open to removing that feature and seeing what breaks in the
test cases.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 05:11  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-23 05:11 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Feb 22, 2017 at 6:15 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> On Wed, Feb 22, 2017 at 5:59 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
>> Ah, I see why *that* wants to know about it ... I think.  I suppose you're
>> arguing that variable expansion shouldn't be able to insert, say, an \else
>> in a non-active branch?  Maybe, but if it can insert an \else in an active
>> branch, then why not non-active too?  Seems a bit inconsistent.
>>
>
> The major reason was avoiding situations like what Daniel showed: where
> value of a variable that is meaningless/undefined in the current
> false-block context gets expanded anyway, and thus code inside a false
> block has effects outside of that block. Granted, his example was
> contrived. I'm open to removing that feature and seeing what breaks in the
> test cases.
>


Welcome to v15, highlights:
- all conditional data structure management moved to conditional.h and
conditional.c
- conditional state lives in mainloop.c and is passed to
HandleSlashCommands, exec_command and get_prompt as needed
- no more pset.active_branch, uses conditional_active(conditional_stack)
instead
- PsqlScanState no longer has branching state
- Implements the %R '@' prompt on false branches.
- Variable expansion is never suppressed even in false blocks, regression
test edited to reflect this.
- ConditionalStack could morph into PsqlFileState without too much work.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..dac8e37 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+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 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..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/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..9d3069e 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -10,12 +10,13 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "input.h"
 #include "prompt.h"
 #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 +24,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, 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 +67,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 +87,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 +141,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 +170,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 +326,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 +373,7 @@ MainLoop(FILE *source)
 
 				/* execute backslash command */
 				slashCmdStatus = HandleSlashCmds(scan_state,
+												 cond_stack,
 												 query_buf->len > 0 ?
 												 query_buf : previous_buf);
 
@@ -358,7 +389,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 +398,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 +461,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 +482,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/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..2c56e3f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,86 @@ 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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..69fc57c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,82 @@ 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_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

Attachments:

  [text/plain] 0001.if_endif.v15.diff (24.6K, ../../CADkLM=d3dYe-Uma-0Z+3F7ERDYC+_CFFmamAA8nHP5KSoEaD7g@mail.gmail.com/3-0001.if_endif.v15.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..dac8e37 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+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 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..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/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..9d3069e 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -10,12 +10,13 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "input.h"
 #include "prompt.h"
 #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 +24,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, 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 +67,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 +87,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 +141,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 +170,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 +326,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 +373,7 @@ MainLoop(FILE *source)
 
 				/* execute backslash command */
 				slashCmdStatus = HandleSlashCmds(scan_state,
+												 cond_stack,
 												 query_buf->len > 0 ?
 												 query_buf : previous_buf);
 
@@ -358,7 +389,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 +398,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 +461,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 +482,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/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 026a4f0..2c56e3f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,86 @@ 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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..69fc57c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,82 @@ 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_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 06:43  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-23 06:43 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Welcome to v15, highlights:

Files "conditional.h" and "conditional.c" are missing from the patch.

Also, is there a particular reason why tap test have been removed?

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 15:05  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-23 15:05 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> Files "conditional.h" and "conditional.c" are missing from the patch.
>
> Also, is there a particular reason why tap test have been removed?


That would be because I diffed against my last commit, not the master
branch, sigh.

v16 is everything v15 promised to be.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..dac8e37 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+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 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..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..1875329
--- /dev/null
+++ b/src/bin/psql/conditional.c
@@ -0,0 +1,107 @@
+/*
+ * 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 (cstack->head != NULL)
+	{
+		IfStackElem *p = cstack->head;
+		cstack->head = cstack->head->next;
+		free(p);
+	}
+	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..bf43c6b 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..2c56e3f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,86 @@ 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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..69fc57c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,82 @@ 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_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

Attachments:

  [text/plain] 0001.if_endif.v16.diff (30.4K, ../../CADkLM=fx7aTqNCXyfC6Z6yNYTsjYVoZ2h+8QBZKkqdRXVycQWQ@mail.gmail.com/3-0001.if_endif.v16.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..dac8e37 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,79 @@ 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
+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 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..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..1875329
--- /dev/null
+++ b/src/bin/psql/conditional.c
@@ -0,0 +1,107 @@
+/*
+ * 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 (cstack->head != NULL)
+	{
+		IfStackElem *p = cstack->head;
+		cstack->head = cstack->head->next;
+		free(p);
+	}
+	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..bf43c6b 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..2c56e3f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,86 @@ 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_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..69fc57c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,82 @@ 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_CONTEXT
 
 \set SHOW_CONTEXT never


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 15:28  Daniel Verite <daniel@manitou-mail.org>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-02-23 15:28 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

	Tom Lane wrote:

> Ah, I see why *that* wants to know about it ... I think.  I suppose you're
> arguing that variable expansion shouldn't be able to insert, say, an \else
> in a non-active branch?  Maybe, but if it can insert an \else in an active
> branch, then why not non-active too?  Seems a bit inconsistent.

Are we sold on the idea that conditionals should be implemented
by meta-commands, rather than for example terminal symbols of
a new grammar on top of the existing?

To recall the context, psql variables are really macros that may
contain meta-commands, and when they do they're essentially
injected and executed at the point of interpolation. That's more
or less what started this thread: demo'ing how we could exit
conditionally by injecting '\q' or nothing into a variable, and
saying that even if doable it was pretty weird, and it would be
better to have real conditional structures instead.

But when conditional structures are implemented as
meta-commands, there's the problem that this structure
can be generated on the fly too, which in a way is no less weird.
While I think that the introduction of conditionals in
psql is great, I'm getting doubtful about that part.
Are there popular script languages or preprocessors
that accept variables/macros instead of symbols to structure
the flow of instructions? I can't think of any myself.


Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 17:52  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-23 17:52 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Daniel,

>> Ah, I see why *that* wants to know about it ... I think.  I suppose you're
>> arguing that variable expansion shouldn't be able to insert, say, an \else
>> in a non-active branch?  Maybe, but if it can insert an \else in an active
>> branch, then why not non-active too?  Seems a bit inconsistent.
>
> Are we sold on the idea that conditionals should be implemented
> by meta-commands, rather than for example terminal symbols of
> a new grammar on top of the existing?

I would say that this already exists server-side, and it is named 
PL/pgSQL:-)

I think that once psql has started with \xxx commands, then client-side 
extensions must stick with it till the end of time.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 19:55  Pavel Stehule <pavel.stehule@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Pavel Stehule @ 2017-02-23 19:55 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Tom Lane <tgl@sss.pgh.pa.us>; Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

2017-02-23 18:52 GMT+01:00 Fabien COELHO <coelho@cri.ensmp.fr>:

>
> Hello Daniel,
>
> Ah, I see why *that* wants to know about it ... I think.  I suppose you're
>>> arguing that variable expansion shouldn't be able to insert, say, an
>>> \else
>>> in a non-active branch?  Maybe, but if it can insert an \else in an
>>> active
>>> branch, then why not non-active too?  Seems a bit inconsistent.
>>>
>>
>> Are we sold on the idea that conditionals should be implemented
>> by meta-commands, rather than for example terminal symbols of
>> a new grammar on top of the existing?
>>
>
> I would say that this already exists server-side, and it is named
> PL/pgSQL:-)
>
> I think that once psql has started with \xxx commands, then client-side
> extensions must stick with it till the end of time.


+1

we don't need strong client side scripting language - it should be just
simple.

Pavel

>
>
> --
> Fabien.
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-23 21:46  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-23 21:46 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v16 is everything v15 promised to be.

My 0.02€:

Patch applies, make check ok, psql make check ok as well.

>> Welcome to v15, highlights:
>> - all conditional data structure management moved to conditional.h and
>>   conditional.c

Indeed.

I cannot say that I find it better, but (1) Tom did required it and (2) it 
still works:-)

If the stack stuff had stayed in "fe_utils", it would have been easy to 
reuse them in pgbench where they might be useful... But who cares?

>> - conditional state lives in mainloop.c and is passed to
>>   HandleSlashCommands, exec_command and get_prompt as needed

Same.

>> - no more pset.active_branch, uses conditional_active(conditional_stack)
>>   instead

Same.

>> - PsqlScanState no longer has branching state

Indeed.

>> - Implements the %R '@' prompt on false branches.

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.

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

>> - Variable expansion is never suppressed even in false blocks,
>>   regression test edited to reflect this.

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

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?

>> - ConditionalStack could morph into PsqlFileState without too much
>>   work.

Probably.

Code details:

Add space after comma when calling send_query.

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

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

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-24 00:06  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-24 00:06 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> 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

Attachments:

  [text/plain] 0001.if_endif.v17.diff (31.6K, ../../CADkLM=c_DDcUXadhfPL4Qcb9QxWbwP6OW_pL=TffLGw=PDiWPQ@mail.gmail.com/3-0001.if_endif.v17.diff)
  download | inline diff:
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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-24 06:53  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-24 06:53 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


About v17:

Patch applies, "make check" & psql "make check" ok.

>> ... '@' [...] I noticed that it takes precedence over '!'. [...]
>
> 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. [...]

Ok.

>> 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.

Typo "unterminted".

I think I found an issue while testing in interactive:

  calvin=# \if false
  calvin@#   \if false
  calvin@#     \echo false-false
    command ignored, use \endif or Ctrl-C to exit current branch.
  calvin@#   \endif
  calvin=#   \echo in false
    in false

The \if within the \if false branch is not tallied properly? Am I missing 
something?

Maybe more test cases should be added to check that nesting checks do work 
properly?

>> 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.

I'm not sure about the ", however, " commas, but I'm sure that I do not 
know English punctuation rules:-)

Maybe the sentence could be cut in shorter pieces.

I think that the fact that "if" commands are checked for proper nesting 
could be kept in the explanation.

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

Ok.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-26 01:54  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-26 01:54 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> Typo "unterminted".
>

Fixed.


> The \if within the \if false branch is not tallied properly? Am I missing
> something?
>

Nope, you found a bug. FIxed. Test-case added.


> 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.
>>
>
> I'm not sure about the ", however, " commas, but I'm sure that I do not
> know English punctuation rules:-)
>

Re-worded it again for shorter sentences. Re-mentioned that conditionals
are still checked for proper nesting.

* Changed comments to reflect that \if always evalutes <expr> even in a
false branch
* Changed \elif to first check if the command is in a proper \if block
before evaluating the expression. The invalid boolean message would mask
the bigger problem.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..27824d9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,7 +3780,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..b57460c 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,144 @@ 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)
+	{
+		bool if_true = false;
+		success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+		if (success)
+		{
+			ifState if_state = IFSTATE_IGNORED;
+			if (conditional_active(cstack))
+				if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+			conditional_stack_push(cstack, if_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		ifState if_state = conditional_stack_peek(cstack);
+		bool elif_true = false;
+
+		switch (if_state)
+		{
+			/* check for cases where \elif is invalid */
+			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:
+				/* \elif belongs here, evaluate the expression */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					switch (if_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 this if-endif,
+							 * must ignore the rest until \endif
+							 */
+							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 (elif_true)
+								conditional_stack_poke(cstack, IFSTATE_TRUE);
+							break;
+						default:
+							/* error cases all previously ruled out */
+							break;
+					}
+				}
+				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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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

Attachments:

  [text/plain] 0001.if_endif.v18.diff (32.1K, ../../CADkLM=edWn9Kfj10BUAr4VTkLxAmkAc_u0_6AfjHTBC6phmhRQ@mail.gmail.com/3-0001.if_endif.v18.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..27824d9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,7 +3780,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..b57460c 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,144 @@ 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)
+	{
+		bool if_true = false;
+		success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+		if (success)
+		{
+			ifState if_state = IFSTATE_IGNORED;
+			if (conditional_active(cstack))
+				if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+			conditional_stack_push(cstack, if_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		ifState if_state = conditional_stack_peek(cstack);
+		bool elif_true = false;
+
+		switch (if_state)
+		{
+			/* check for cases where \elif is invalid */
+			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:
+				/* \elif belongs here, evaluate the expression */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+				{
+					switch (if_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 this if-endif,
+							 * must ignore the rest until \endif
+							 */
+							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 (elif_true)
+								conditional_stack_poke(cstack, IFSTATE_TRUE);
+							break;
+						default:
+							/* error cases all previously ruled out */
+							break;
+					}
+				}
+				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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-26 07:47  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-02-26 07:47 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

About v18: Patch applies, make check ok, psql tap tests ok.


ISTM that contrary to the documentation "\elif something" is not evaluated 
in all cases, and the resulting code is harder to understand with a nested 
switch and condition structure:

   switch
   default
     read
     if
        switch

I wish (this is a personal taste) it could avoid switch-nesting on the 
very same value. It should also conform to the documentation.

If there is no compelling reason for the switch-nesting, I would suggest 
to move the read_boolean_expression before the swich, to deal with error 
immediately there, and then to have just one switch.

Alternatively if the structure must really be kept, then deal with errors 
in a first switch, read value *after* switch and deal with other errors 
there, then start a second switch, and adjust the documentation 
accordingly?

   switch
     errors
   read
   if
     errors
   // no error
   switch


Also, the %R documentation has single line marker '^' before not executed 
'@', but it is the reverse in the code.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-02-26 20:07  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-02-26 20:07 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sun, Feb 26, 2017 at 2:47 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> About v18: Patch applies, make check ok, psql tap tests ok.
>
>
> ISTM that contrary to the documentation "\elif something" is not evaluated
> in all cases, and the resulting code is harder to understand with a nested
> switch and condition structure:
>
>   switch
>   default
>     read
>     if
>        switch
>
> I wish (this is a personal taste) it could avoid switch-nesting on the
> very same value. It should also conform to the documentation.
>

I wasn't too happy with it, but I figured it would spark discussion. I
succeeded.


>
> If there is no compelling reason for the switch-nesting, I would suggest
> to move the read_boolean_expression before the swich, to deal with error
> immediately there, and then to have just one switch.
>

I thought about doing it that way. However, in the case of:


\set x invalid

\if true

\else
\elif :x

\endif


The error has already "happened" at line 4, char 5, and it doesn't matter
what expression follows, you will get an error.  But because
read_boolean_expression() can emit errors, you would see the error saying
"invalid" isn't a valid boolean expression, and then see another error
saying that the \elif was out of place. If we suppress
read_boolean_expression()'s error reporting, then we have to re-create that
error message ourselves, or be fine with the error message on invalid
\elifs being inconsistent with invalid \ifs.

Similar to your suggestion below, we could encapsulate the first switch
into a function valid_elif_context(ConditionalStack), which might make the
code cleaner, but would make it harder to see that all swtich-cases are
covered between the two. That might be a tradeoff we want to make.


>
> Alternatively if the structure must really be kept, then deal with errors
> in a first switch, read value *after* switch and deal with other errors
> there, then start a second switch, and adjust the documentation accordingly?
>
>   switch
>     errors
>   read
>   if
>     errors
>   // no error
>   switch
>


How would the documentation have to change?

Also, the %R documentation has single line marker '^' before not executed
> '@', but it is the reverse in the code.


Noted and fixed in the next patch, good catch.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-01 04:02  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-01 04:02 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
>>
>> Alternatively if the structure must really be kept, then deal with errors
>> in a first switch, read value *after* switch and deal with other errors
>> there, then start a second switch, and adjust the documentation accordingly?
>>
>>   switch
>>     errors
>>   read
>>   if
>>     errors
>>   // no error
>>   switch
>>
>
>
it's now something more like

switch
  error-conditions
if no-errors
  read
  if was a boolean
  switch last-state

It doesn't strike me as much cleaner, but it's no worse, either.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..5dcde39 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,154 @@ 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)
+	{
+		bool if_true = false;
+		success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+		if (success)
+		{
+			ifState if_state = IFSTATE_IGNORED;
+			if (conditional_active(cstack))
+				if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+			conditional_stack_push(cstack, if_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		ifState if_state = conditional_stack_peek(cstack);
+
+		/* check for invalid \elif contexts */
+		switch (if_state)
+		{
+			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;
+		}
+
+		if (success)
+		{
+			/*
+			 * valid \elif context, check for valid expression
+			 */
+			bool elif_true = false;
+			success = read_boolean_expression(scan_state, "\\elif <expr>",
+												&elif_true);
+			if (success)
+			{
+				/*
+				 * got a valid boolean, what to do with it depends on current
+				 * state 
+				 */
+				switch (if_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 this if-endif,
+						 * must ignore the rest until \endif
+						 */
+						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 (elif_true)
+							conditional_stack_poke(cstack, IFSTATE_TRUE);
+						break;
+					default:
+						/* error cases all previously ruled out */
+						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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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

Attachments:

  [text/plain] 0001.if_endif.v19.diff (32.3K, ../../CADkLM=eLwZHnK82E=qtLMxWy_UDJe-XUMUK1xABPgSP386H4Qw@mail.gmail.com/3-0001.if_endif.v19.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..5dcde39 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,154 @@ 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)
+	{
+		bool if_true = false;
+		success = read_boolean_expression(scan_state, "\\if <expr>", &if_true);
+		if (success)
+		{
+			ifState if_state = IFSTATE_IGNORED;
+			if (conditional_active(cstack))
+				if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+			conditional_stack_push(cstack, if_state);
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		ifState if_state = conditional_stack_peek(cstack);
+
+		/* check for invalid \elif contexts */
+		switch (if_state)
+		{
+			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;
+		}
+
+		if (success)
+		{
+			/*
+			 * valid \elif context, check for valid expression
+			 */
+			bool elif_true = false;
+			success = read_boolean_expression(scan_state, "\\elif <expr>",
+												&elif_true);
+			if (success)
+			{
+				/*
+				 * got a valid boolean, what to do with it depends on current
+				 * state 
+				 */
+				switch (if_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 this if-endif,
+						 * must ignore the rest until \endif
+						 */
+						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 (elif_true)
+							conditional_stack_poke(cstack, IFSTATE_TRUE);
+						break;
+					default:
+						/* error cases all previously ruled out */
+						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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-01 08:07  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-01 08:07 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> It doesn't strike me as much cleaner, but it's no worse, either.

Hmmm.

The "if (x) { x = ... ; if (x) {" does not help much to improve 
readability and understandability...

My 0.02€ about v19:

If there are two errors, I do not care which one is shown, both will have 
to be fixed anyway in the end... So I would suggest to choose the simplest 
possible implementation:

   on elif:
     always eval expression
       => possible eval error
     switch
       => including detecting misplaced elif errors

If the second error must absolutely be shown in all cases, then add a 
second misplaced elif detection in the eval expression failure branch:

   on elif
     always eval
     if (eval failed)
       also checked for misplaced (hey user, you have 2 errors in fact...)
       bye bye...
     // else eval was fine
     switch
       including misplaced elif detection

If the committer is angry at these simple approach, then revert to the 
strange looking and hard to understand switch-if-switch solution (~ v18, 
or some simplified? v19), but I do not think the be weak benefit is worth 
the code complexity.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-01 16:34  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-01 16:34 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Mar 1, 2017 at 3:07 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> It doesn't strike me as much cleaner, but it's no worse, either.
>>
>
> Hmmm.
>
> The "if (x) { x = ... ; if (x) {" does not help much to improve
> readability and understandability...
>
> My 0.02€ about v19:
>
> If there are two errors, I do not care which one is shown, both will have
> to be fixed anyway in the end... So I would suggest to choose the simplest
> possible implementation:
>
>   on elif:
>     always eval expression
>       => possible eval error
>     switch
>       => including detecting misplaced elif errors
>
> If the second error must absolutely be shown in all cases, then add a
> second misplaced elif detection in the eval expression failure branch:
>
>   on elif
>     always eval
>     if (eval failed)
>       also checked for misplaced (hey user, you have 2 errors in fact...)
>       bye bye...
>     // else eval was fine
>     switch
>       including misplaced elif detection
>
> If the committer is angry at these simple approach, then revert to the
> strange looking and hard to understand switch-if-switch solution (~ v18, or
> some simplified? v19), but I do not think the be weak benefit is worth the
> code complexity.
>
> --
> Fabien.


Just to make things easy for the committer, the existing code only shows
the user one error:

on elif
  if misplaced elif
     misplaced elif error
  else
     eval expression
       => possible eval error
     set new status if eval fine


The issue at hand being the benefit to the user vs code complexity.

So, shall we send this off to the committers and let them decide?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-01 17:23  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-01 17:23 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> on elif
>  if misplaced elif
>     misplaced elif error
>  else
>     eval expression
>       => possible eval error
>     set new status if eval fine

Currently it is really:

   switch (state) {
   case NONE:
   case ELSE_TRUE:
   case ELSE_FALSE:
      success = false;
      show some error
   default:
   }
   if (success) {
     success = evaluate_expression(...);
     if (success) {
        switch (state) {
        case ...:
        default:
        }
     }
   }

Which I do not find so neat. The previous one with nested switch-if-switch 
looked as bad.

> The issue at hand being the benefit to the user vs code complexity.

Hmmm.

One of my point is that I do not really see the user benefit... for me the 
issue is to have no user benefit and code complexity.

The case we are discussing is for the user who decides to write code with 
*two* errors on the same line:

   \if good-condition
   \else
   \elif bad-condition
   \endif

with an added complexity to show the elif bad position error first. Why 
should we care so much for such a special case?

Maybe an alternative could be to write simpler code anyway, somehow like 
it was before:

   // on "elif"
   switch (peek(state)) {
   case NONE:       error;
   case ELSE_TRUE:  error;
   case ELSE_FALSE: error;
   case IGNORED:    break;
   case TRUE:       poke IGNORED;
   case FALSE:
                    success = evaluate(&is_true)
                    if (!success)
                      error;
                    else if (is_true)
                        poke TRUE
   default:         error;
   }

The only difference is that the evaluation is not done when it is not 
needed (what a draw back) but ISTM that it is significantly easier to 
understand and maintain.

Now if you want to require committer opinion on this one, fine with me.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-01 18:01  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-01 18:01 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Wed, Mar 1, 2017 at 12:23 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> on elif
>>  if misplaced elif
>>     misplaced elif error
>>  else
>>     eval expression
>>       => possible eval error
>>     set new status if eval fine
>>
>
> Currently it is really:
>
>   switch (state) {
>   case NONE:
>   case ELSE_TRUE:
>   case ELSE_FALSE:
>      success = false;
>      show some error
>   default:
>   }
>   if (success) {
>     success = evaluate_expression(...);
>     if (success) {
>        switch (state) {
>        case ...:
>        default:
>        }
>     }
>   }
>
> Which I do not find so neat. The previous one with nested switch-if-switch
> looked as bad.


That is accurate. The only positive it has is that the user only
experiences one error, and it's the first error that was encountered if
reading top-to-bottom, left to right. It is an issue of which we prioritize
- user experience or simpler code.

Now if you want to require committer opinion on this one, fine with me.


Rather than speculate on what a committer thinks of this edge case (and
making a patch for each possible theory), I'd rather just ask them what
their priorities are and which user experience they favor.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-02 06:23  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-02 06:23 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> That is accurate. The only positive it has is that the user only
> experiences one error, and it's the first error that was encountered if
> reading top-to-bottom, left to right. It is an issue of which we prioritize
> - user experience or simpler code.

Hmmm. The last simpler structure I suggested, which is basically the one 
used in your code before the update, does check for the structure error 
first. The only drawback is that the condition is only evaluated when 
needed, which is an issue we can cope with, IMO.

>> Now if you want to require committer opinion on this one, fine with me.
>
> Rather than speculate on what a committer thinks of this edge case (and
> making a patch for each possible theory), I'd rather just ask them what
> their priorities are and which user experience they favor.

ISTM that the consistent message by Robert & Tom was to provide simpler 
code even if the user experience is somehow degraded, as they required 
that user-friendly features were removed (eg trying to be nicer about 
structural syntax errors, barking in interactive mode so that the user 
always knows the current status, providing a detailed status indicator in 
the prompt...).

Now committers can change their opinions, it is their privilege:-)

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-02 06:54  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-02 06:54 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Thu, Mar 2, 2017 at 1:23 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> That is accurate. The only positive it has is that the user only
>> experiences one error, and it's the first error that was encountered if
>> reading top-to-bottom, left to right. It is an issue of which we
>> prioritize
>> - user experience or simpler code.
>>
>
> Hmmm. The last simpler structure I suggested, which is basically the one
> used in your code before the update, does check for the structure error
> first. The only drawback is that the condition is only evaluated when
> needed, which is an issue we can cope with, IMO.


Tom was pretty adamant that invalid commands are not executed. So in a case
like this, with ON_ERROR_STOP off:

\if false
\echo 'a'
\elif true
\echo 'b'
\elif invalid
\echo 'c'
\endif

Both 'b' and 'c' should print, because "\elif invalid" should not execute.
The code I had before was simpler, but it missed that.


>
> Now if you want to require committer opinion on this one, fine with me.
>>>
>>
>> Rather than speculate on what a committer thinks of this edge case (and
>> making a patch for each possible theory), I'd rather just ask them what
>> their priorities are and which user experience they favor.
>>
>
> ISTM that the consistent message by Robert & Tom was to provide simpler
> code even if the user experience is somehow degraded, as they required that
> user-friendly features were removed (eg trying to be nicer about structural
> syntax errors, barking in interactive mode so that the user always knows
> the current status, providing a detailed status indicator in the prompt...).
>

Ok, so here's one idea I tossed around, maybe this will strike the right
balance for you.

If I create a function like this:

static boolean
is_valid_else_context(IfState if_state, const char *cmd)
{
    /* check for invalid \else / \elif contexts */

    switch (if_state)

    {
        case IFSTATE_NONE:
            /* not in an \if block */
            psql_error("\\%s: no matching \\if\n", cmd);
            return false;
            break;
        case IFSTATE_ELSE_TRUE:
        case IFSTATE_ELSE_FALSE:
            psql_error("\\%s: cannot occur after \\else\n", cmd);
            return false;
            break;
        default:
            break;
    }
    return true;
}



Then the elif block looks something like this:

    else if (strcmp(cmd, "elif") == 0)
    {
        ifState if_state = conditional_stack_peek(cstack);

        if (is_valid_else_context(if_state, "elif"))
        {
            /*
             * valid \elif context, check for valid expression
             */
            bool elif_true = false;
            success = read_boolean_expression(scan_state, "\\elif <expr>",
                                                &elif_true);
            if (success)
            {
                /*
                 * got a valid boolean, what to do with it depends on
current
                 * state
                 */
                switch (if_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 this if-endif,
                         * must ignore the rest until \endif
                         */
                        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 (elif_true)
                            conditional_stack_poke(cstack, IFSTATE_TRUE);
                        break;
                    default:
                        /* error cases all previously ruled out */
                        break;
                }
            }
        }
        else
            success = false;
        psql_scan_reset(scan_state);
    }


This is functionally the same as my latest patch, but the ugliness of
switching twice on if_state is hidden.

As an added benefit, the "else"-handling code gets pretty simple because it
can leverage that same function.

Does that handle your objections?

p.s.  do we try to avoid constructs like    if (success = my_function(var1,
var2))   ?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-02 10:04  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-02 10:04 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> Tom was pretty adamant that invalid commands are not executed. So in a case
> like this, with ON_ERROR_STOP off:
>
> \if false
> \echo 'a'
> \elif true
> \echo 'b'
> \elif invalid
> \echo 'c'
> \endif
>
> Both 'b' and 'c' should print, because "\elif invalid" should not execute.
> The code I had before was simpler, but it missed that.

Hmmm. You can still have it with one switch, by repeating the evaluation 
under true and ignore, even if the value is not used:

   switch(state)
   {
     case NONE: error;
     case ELSE_TRUE: error;
     case ELSE_FALSE: error;
     case IF_TRUE:
         if (eval())
           ...
         else error;
         break;
     case IF_FALSE:
         if (eval())
           ...
         else error;
         break;
     case IGNORE:
         if (eval())
           ...
         else error;
         break;
     }

> Ok, so here's one idea I tossed around, maybe this will strike the right
> balance for you.  If I create a function like this: [...]
>
> Does that handle your objections?

For me, it is only slightly better: I think that for helping understanding 
and maintenance, the automaton state transitions should be all clear and 
loud in just one place, so I would really like to see a single common 
structure:

   if (is "if") switch on all states;
   else if (is "elif") switch on all states;
   else if (is "else") switch on all states;
   else if (is "endif") switch on all states;

And minimal necessary error handling around that.

Your suggestion does not achieve this, although I agree that the code 
structure would be cleaner thanks to the function.

> p.s.  do we try to avoid constructs like    if (success = my_function(var1,
> var2))   ?

I think it is allowed because I found some of them with grep (libpq, ecpg, 
postmaster, pg_dump, pg_upgrade...). They require added parentheses around 
the assignment:

   if ((success = eval())) ...

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-02 19:47  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-02 19:47 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> For me, it is only slightly better: I think that for helping understanding
> and maintenance, the automaton state transitions should be all clear and
> loud in just one place, so I would really like to see a single common
> structure:
>
>   if (is "if") switch on all states;
>   else if (is "elif") switch on all states;
>   else if (is "else") switch on all states;
>   else if (is "endif") switch on all states;
>
> And minimal necessary error handling around that.
>
>
v20: attempt at implementing the switch-on-all-states style.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..ee8e2ff 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,159 @@ 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)
+	{
+		bool if_true = false;
+
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+			case IFSTATE_FALSE:
+			case IFSTATE_ELSE_FALSE:
+				/*
+				 * check for valid expression, start a new if-block, but
+				 * the whole block will be ignored
+				 */
+				success = read_boolean_expression(scan_state, "\\if <expr>",
+													&if_true);
+				if (success)
+					conditional_stack_push(cstack, IFSTATE_IGNORED);
+				break;
+			default:
+				success = read_boolean_expression(scan_state, "\\if <expr>",
+													&if_true);
+				if (success)
+				{
+					if (if_true)
+						conditional_stack_push(cstack, IFSTATE_TRUE);
+					else
+						conditional_stack_push(cstack, IFSTATE_FALSE);
+				}
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		bool elif_true = false;
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, only test for valid expression.
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of this if-endif, test for valid
+				 * expression, but then ignore the rest until \endif
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+					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.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success && elif_true)
+					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;
+		}
+		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
+		 */
+		switch (conditional_stack_peek(cstack))
+		{
+			default:
+				success = conditional_stack_pop(cstack);
+				if (!success)
+					psql_error("\\endif: no matching \\if\n");
+			break;
+		}
+		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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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

Attachments:

  [text/plain] 0001.if_endif.v20.diff (32.7K, ../../CADkLM=e3vpXixqQd9X_wD0LVL1vkrq4oPu33xzsTr3CokSRnWA@mail.gmail.com/3-0001.if_endif.v20.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..ee8e2ff 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,159 @@ 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)
+	{
+		bool if_true = false;
+
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+			case IFSTATE_FALSE:
+			case IFSTATE_ELSE_FALSE:
+				/*
+				 * check for valid expression, start a new if-block, but
+				 * the whole block will be ignored
+				 */
+				success = read_boolean_expression(scan_state, "\\if <expr>",
+													&if_true);
+				if (success)
+					conditional_stack_push(cstack, IFSTATE_IGNORED);
+				break;
+			default:
+				success = read_boolean_expression(scan_state, "\\if <expr>",
+													&if_true);
+				if (success)
+				{
+					if (if_true)
+						conditional_stack_push(cstack, IFSTATE_TRUE);
+					else
+						conditional_stack_push(cstack, IFSTATE_FALSE);
+				}
+				break;
+		}
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		bool elif_true = false;
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, only test for valid expression.
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of this if-endif, test for valid
+				 * expression, but then ignore the rest until \endif
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+					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.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success && elif_true)
+					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;
+		}
+		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
+		 */
+		switch (conditional_stack_peek(cstack))
+		{
+			default:
+				success = conditional_stack_pop(cstack);
+				if (!success)
+					psql_error("\\endif: no matching \\if\n");
+			break;
+		}
+		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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-03 06:19  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-03 06:19 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v20: attempt at implementing the switch-on-all-states style.

For the elif I think it is both simpler and better like that. Whether 
committer will agree is an unkown, as always.

For endif, I really exagerated, "switch { defaut: " is too much, please 
accept my apology. Maybe just do the pop & error reporting?

For if, the evaluation & error could be moved before the switch, which may 
contain only the new state setting decision, and the push after the 
switch? Also, I would suggest to use default only to detect an unexpected 
state error, and list all other states explicitely.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-03 06:25  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 3 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-03 06:25 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org



> For endif, I really exagerated, "switch { defaut: " is too much, please 
> accept my apology. Maybe just do the pop & error reporting?

Or maybe be more explicit:

   switch (current state)
   case NONE:
      error no matching if;
   case ELSE_FALSE:
   case ELSE_TRUE:
   case ...:
      pop;
      Assert(success);

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-03 06:53  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-03 06:53 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Mar 3, 2017 at 1:25 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
>
> For endif, I really exagerated, "switch { defaut: " is too much, please
>> accept my apology. Maybe just do the pop & error reporting?
>>
>
It seemed like overkill, but I decided to roll with it.


>
> Or maybe be more explicit:
>
>   switch (current state)
>   case NONE:
>      error no matching if;
>   case ELSE_FALSE:
>   case ELSE_TRUE:
>   case ...:
>      pop;
>      Assert(success);


the pop() function tests for an empty stack, so this switch is
double-testing, but it's also no big deal, so here you go...
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..ac096bd 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,157 @@ 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)
+	{
+		bool if_true = false;
+		ifState new_if_state;
+		success = read_boolean_expression(scan_state, "\\if <expr>",
+											&if_true);
+
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+			case IFSTATE_FALSE:
+			case IFSTATE_ELSE_FALSE:
+				/* new if-block, expression result is ignored */
+				new_if_state = IFSTATE_IGNORED;
+				break;
+			default:
+				/* new if-block, expression result matters */
+				new_if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+				break;
+		}
+
+		/* only start if a new if-block if the expression was valid */
+		if (success)
+			conditional_stack_push(cstack, new_if_state);
+
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		bool elif_true = false;
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, only test for valid expression.
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of this if-endif, test for valid
+				 * expression, but then ignore the rest until \endif
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+					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.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success && elif_true)
+					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;
+		}
+		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
+		 */
+		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;
+		}
+		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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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

Attachments:

  [text/plain] 0001.if_endif.v21.diff (32.7K, ../../CADkLM=dLtw4GtfbZM=jTgsp3nQ1r6DcSque-DxV7fJ0rYqhyRw@mail.gmail.com/3-0001.if_endif.v21.diff)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..9d245a9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,83 @@ 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 for query/command
+        completion, but any queries are not sent to the server, and any
+        commands other than conditionals (<command>\if</command>,
+        <command>\elif</command>, <command>\else</command>,
+        <command>\endif</command>) are ignored. Conditional commands are
+        still checked for valid nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> <replaceable class="parameter">filename</replaceable></term>
@@ -3703,8 +3780,9 @@ 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,
-        or <literal>!</literal> if the session is disconnected from the
+        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
         depends on why <application>psql</application> expects more input:
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..ac096bd 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,157 @@ 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)
+	{
+		bool if_true = false;
+		ifState new_if_state;
+		success = read_boolean_expression(scan_state, "\\if <expr>",
+											&if_true);
+
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+			case IFSTATE_FALSE:
+			case IFSTATE_ELSE_FALSE:
+				/* new if-block, expression result is ignored */
+				new_if_state = IFSTATE_IGNORED;
+				break;
+			default:
+				/* new if-block, expression result matters */
+				new_if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+				break;
+		}
+
+		/* only start if a new if-block if the expression was valid */
+		if (success)
+			conditional_stack_push(cstack, new_if_state);
+
+		psql_scan_reset(scan_state);
+	}
+
+	/*
+	 * \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)
+	{
+		bool elif_true = false;
+		switch (conditional_stack_peek(cstack))
+		{
+			case IFSTATE_IGNORED:
+				/*
+				 * inactive branch, only test for valid expression.
+				 * either if-endif already had a true block,
+				 * or whole parent block is false.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				break;
+			case IFSTATE_TRUE:
+				/*
+				 * just finished true section of this if-endif, test for valid
+				 * expression, but then ignore the rest until \endif
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success)
+					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.
+				 */
+				success = read_boolean_expression(scan_state, "\\elif <expr>",
+													&elif_true);
+				if (success && elif_true)
+					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;
+		}
+		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
+		 */
+		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;
+		}
+		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..aa54184 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,109 @@ 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
+-- 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 d823d11..c35516e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,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 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
+
+-- 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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-03 08:18  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-03 08:18 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


About v21:

Patch applies with some offset, make check ok, psql tap tests ok.

I also did some interactive tests which behaved as I was expecting.

I'm ok with this patch. I think that the very simple automaton code 
structure achieved is worth the very few code duplications. It is also 
significantly shorter than the nested if/switch variants, and it does 
exactly what Tom and Robert wished with respect to errors, so I think that 
this is a good compromise.

A tiny detail about "default". I would have added a comment when it is 
expected to be dead code (else, elif), and I would have put the list of 
matching states explicitely otherwise (if, endif) otherwise the reader has 
to remember what the other states are. Probably it is me being really too 
peckish, if at all possible:-)

I've turned the patch as ready, again.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-11 21:17  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-11 21:17 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> [ 0001.if_endif.v21.diff ]

Starting to poke at this... the proposal to add prove checks for psql
just to see whether \if respects ON_ERROR_STOP seems like an incredibly
expensive way to test a rather minor point.  On my machine, "make check"
in bin/psql goes from zero time to close to 8 seconds.  I'm not really
on board with adding that kind of time to every buildfarm run for the
foreseeable future just for this.

Couldn't we get close to the same coverage by adding a single-purpose
test script to the main regression tests?  Along the lines of

     \set ON_ERROR_STOP 1
     \if invalid
     \echo should not get here
     \endif
     \echo should not get here either

You could imagine just dropping that at the end of psql.sql, but I
think probably a separate script is worth the trouble.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-11 23:34  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-11 23:34 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Mar 11, 2017 at 4:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Corey Huinker <corey.huinker@gmail.com> writes:
> > [ 0001.if_endif.v21.diff ]
>
> Starting to poke at this... the proposal to add prove checks for psql
> just to see whether \if respects ON_ERROR_STOP seems like an incredibly
> expensive way to test a rather minor point.  On my machine, "make check"
> in bin/psql goes from zero time to close to 8 seconds.  I'm not really
> on board with adding that kind of time to every buildfarm run for the
> foreseeable future just for this.
>
> Couldn't we get close to the same coverage by adding a single-purpose
> test script to the main regression tests?  Along the lines of
>
>      \set ON_ERROR_STOP 1
>      \if invalid
>      \echo should not get here
>      \endif
>      \echo should not get here either
>
> You could imagine just dropping that at the end of psql.sql, but I
> think probably a separate script is worth the trouble.
>
>                         regards, tom lane
>


I think I can manage that. Just to be clear, you're asking me to replace
the perl script with one new sql script? If so, there's probably a few
non-on-stop tests in there that might be worth preserving in regression
form.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 00:45  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  2 siblings, 3 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-12 00:45 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> [ 0001.if_endif.v21.diff ]

I had thought that this patch was pretty close to committable, but
the more I poke at it the less I think so.  The technology it uses
for skipping unexecuted script sections has got too many bugs.

* Daniel Verite previously pointed out the desirability of disabling
variable expansion while skipping script.  That doesn't seem to be here,
though there's an apparently-vestigial comment in psql/common.c claiming
that it is.  IIRC, I objected to putting knowledge of ConditionalStack
into the shared psqlscan.l lexer, and I still think that would be a bad
idea; but we need some way to get the lexer to shut that off.  Probably
the best way is to add a passthrough "void *" argument that would let the
get_variable callback function mechanize the rule about not expanding
in a false branch.

* Whether or not you think it's important not to expand skipped variables,
I think that it's critical that skipped backtick expressions not be
executed.  The specific use-case that I'm concerned about is backtick
evals in \if expressions, which are going to be all over the place as
long as we haven't got any native expression eval capability, and will
doubtless remain important even when/if we do.  So in a case like

	\if something
	\elif `expr :var1 + :var2 = :var3`
	\endif

I think it's essential that expr not be called if the first if-condition
succeeded.  (That first condition might be checking whether the vars
contain valid integers, for example.)  The current patch gets this totally
wrong --- not only does it perform the backtick, but \elif complains if
the result isn't a valid bool.  I do not think that a skipped \if or \elif
should evaluate its argument at all.

* The documentation says that an \if or \elif expression extends to the
end of the line, but actually the code is just eating one OT_NORMAL
argument.  That means it's OK to do this:

regression=# \if 1 \echo foo \echo bar \endif
foo
bar
regression=# 

which doesn't seem insane, except that the inverse case is insane:

regression=# \if 0 \echo foo \echo bar \endif
regression@# 

(notice we're not out of the conditional).  Even if we change it to
eat the whole line as argument, this inconsistency will remain:

regression=# \if 1
regression=# \echo foo \endif
foo
regression=# 

(notice we're out of the conditional)

regression=# \if 0
regression@# \echo foo \endif
command ignored, use \endif or Ctrl-C to exit current branch.
regression@# 

(notice we're not out of the conditional)

This inconsistency seems to have to do with the code in HandleSlashCmds
that discards arguments until EOL after a failed backslash command.
You've modified that to also discard arguments after a non-executed
command, and I think that's broken.

* 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.

* I think it's completely wrong to do either resetPQExpBuffer(query_buf)
or psql_scan_reset(scan_state) when deciding a branch is not to be
executed.  Compare these results:

regression=# select (1 +
regression(# \if 1
regression-# \echo foo
foo
regression-# \endif
regression-# 2);
 ?column? 
----------
        3
(1 row)

regression=# select (1 +
regression(# \if 0
regression-# \echo foo
command ignored, use \endif or Ctrl-C to exit current branch.
regression@# \endif
regression=# 2);
ERROR:  syntax error at or near "2"
LINE 1: 2);
        ^
regression=# 

If the first \if doesn't throw away an incomplete query buffer (which it
should not), then surely the second should not either.  Somebody who
actually wants to toss the query buffer can put \r into the appropriate
branch of their \if; it's not for us to force that on them.

* Also, the documentation for psql_scan_reset is pretty clear that it's to
be called when and only when the query buffer is reset, which makes your
calls in the bodies of the conditional commands wrong.  As an example:

regression=# select (1 +
regression(# 2;
regression(# 

(notice we've not sent the known-incomplete command to the server) vs

regression(# \r
Query buffer reset (cleared).
regression=# select (1 +
regression(# \if 1
regression-# \endif
regression-# 2;
ERROR:  syntax error at or near ";"
LINE 2: 2;
         ^
regression=# 

That happens because the \if code gratuituously resets the lexer,
as we can see from the unexpected change in the prompt.

* I'm not on board with having a bad expression result in failing
the \if or \elif altogether.  It was stated several times upthread
that that should be processed as though the result were "false",
and I agree with that.  As it stands, it's completely impossible to
write script code that can cope with possibly-failing expressions,
or even to reason very clearly about what will happen: you can't
know whether a following \else will be honored, for example.
We might as well replace the recommendation to use ON_ERROR_STOP with
a forced abort() for an invalid expression value, because trying to
continue a script with this behavior is entirely useless.


I did some work on the patch before reaching these conclusions,
mostly improving the documentation, getting rid of some unnecessary
#include's, etc.  I've attached that work as far as it went.

			regards, tom lane

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-diff] if_endif.v22.patch (37.7K, ../../13293.1489279510@sss.pgh.pa.us/2-if_endif.v22.patch)
  download | inline diff:
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
*************** hello 10
*** 2064,2069 ****
--- 2064,2165 ----
  
  
        <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>
*************** testdb=&gt; <userinput>INSERT INTO my_ta
*** 3715,3721 ****
          <listitem>
          <para>
          In prompt 1 normally <literal>=</literal>,
!         but <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
--- 3811,3818 ----
          <listitem>
          <para>
          In prompt 1 normally <literal>=</literal>,
!         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,5 ****
--- 3,7 ----
  /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
*************** REFDOCDIR= $(top_srcdir)/doc/src/sgml/re
*** 21,30 ****
  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 \
  	startup.o prompt.o variables.o large_obj.o describe.o \
  	crosstabview.o tab-complete.o \
! 	sql_help.o psqlscanslash.o \
  	$(WIN32RES)
  
  
--- 21,30 ----
  override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
  LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
  
! 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 stringutils.o psqlscanslash.o \
  	$(WIN32RES)
  
  
*************** uninstall:
*** 57,64 ****
--- 57,71 ----
  
  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..359da08 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** typedef enum EditableObjectType
*** 59,64 ****
--- 59,65 ----
  /* 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);
*************** static void checkWin32Codepage(void);
*** 106,111 ****
--- 107,113 ----
  
  backslashResult
  HandleSlashCmds(PsqlScanState scan_state,
+ 				ConditionalStack cstack,
  				PQExpBuffer query_buf)
  {
  	backslashResult status = PSQL_CMD_SKIP_LINE;
*************** HandleSlashCmds(PsqlScanState scan_state
*** 118,124 ****
  	cmd = psql_scan_slash_command(scan_state);
  
  	/* And try to execute it */
! 	status = exec_command(cmd, scan_state, query_buf);
  
  	if (status == PSQL_CMD_UNKNOWN)
  	{
--- 120,126 ----
  	cmd = psql_scan_slash_command(scan_state);
  
  	/* And try to execute it */
! 	status = exec_command(cmd, scan_state, cstack, query_buf);
  
  	if (status == PSQL_CMD_UNKNOWN)
  	{
*************** HandleSlashCmds(PsqlScanState scan_state
*** 129,135 ****
  		status = PSQL_CMD_ERROR;
  	}
  
! 	if (status != PSQL_CMD_ERROR)
  	{
  		/* eat any remaining arguments after a valid command */
  		/* note we suppress evaluation of backticks here */
--- 131,137 ----
  		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 */
*************** read_connect_arg(PsqlScanState scan_stat
*** 191,196 ****
--- 193,222 ----
  	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.
*************** read_connect_arg(PsqlScanState scan_stat
*** 198,209 ****
--- 224,248 ----
  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.
*************** exec_command(const char *cmd,
*** 1008,1013 ****
--- 1047,1202 ----
  		}
  	}
  
+ 	/*
+ 	 * \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)
+ 	{
+ 		bool if_true = false;
+ 		ifState new_if_state;
+ 		success = read_boolean_expression(scan_state, "\\if <expr>",
+ 											&if_true);
+ 
+ 		switch (conditional_stack_peek(cstack))
+ 		{
+ 			case IFSTATE_IGNORED:
+ 			case IFSTATE_FALSE:
+ 			case IFSTATE_ELSE_FALSE:
+ 				/* new if-block, expression result is ignored */
+ 				new_if_state = IFSTATE_IGNORED;
+ 				break;
+ 			default:
+ 				/* new if-block, expression result matters */
+ 				new_if_state = (if_true) ? IFSTATE_TRUE : IFSTATE_FALSE;
+ 				break;
+ 		}
+ 
+ 		/* only start if a new if-block if the expression was valid */
+ 		if (success)
+ 			conditional_stack_push(cstack, new_if_state);
+ 
+ 		psql_scan_reset(scan_state);
+ 	}
+ 
+ 	/*
+ 	 * \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)
+ 	{
+ 		bool elif_true = false;
+ 		switch (conditional_stack_peek(cstack))
+ 		{
+ 			case IFSTATE_IGNORED:
+ 				/*
+ 				 * inactive branch, only test for valid expression.
+ 				 * either if-endif already had a true block,
+ 				 * or whole parent block is false.
+ 				 */
+ 				success = read_boolean_expression(scan_state, "\\elif <expr>",
+ 													&elif_true);
+ 				break;
+ 			case IFSTATE_TRUE:
+ 				/*
+ 				 * just finished true section of this if-endif, test for valid
+ 				 * expression, but then ignore the rest until \endif
+ 				 */
+ 				success = read_boolean_expression(scan_state, "\\elif <expr>",
+ 													&elif_true);
+ 				if (success)
+ 					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.
+ 				 */
+ 				success = read_boolean_expression(scan_state, "\\elif <expr>",
+ 													&elif_true);
+ 				if (success && elif_true)
+ 					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;
+ 		}
+ 		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
+ 		 */
+ 		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;
+ 		}
+ 		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,15 ****
--- 10,16 ----
  
  #include "fe_utils/print.h"
  #include "fe_utils/psqlscan.h"
+ #include "conditional.h"
  
  
  typedef enum _backslashResult
*************** typedef enum _backslashResult
*** 25,30 ****
--- 26,32 ----
  
  
  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 1aa56ab..83ac284 100644
*** a/src/bin/psql/common.c
--- b/src/bin/psql/common.c
*************** setQFout(const char *fname)
*** 119,124 ****
--- 119,129 ----
   * 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
index ...8643ff1 .
*** a/src/bin/psql/conditional.c
--- b/src/bin/psql/conditional.c
***************
*** 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(void)
+ {
+ 	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+ 	cstack->head = NULL;
+ 	return cstack;
+ }
+ 
+ /*
+  * destroy stack
+  */
+ void
+ conditional_stack_destroy(ConditionalStack cstack)
+ {
+ 	while (conditional_stack_pop(cstack))
+ 		continue;
+ 	free(cstack);
+ }
+ 
+ /*
+  * Create a new conditional branch.
+  */
+ void
+ conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+ {
+ 	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+ 	p->if_state = new_state;
+ 	p->next = cstack->head;
+ 	cstack->head = p;
+ }
+ 
+ /*
+  * Destroy the topmost conditional branch.
+  * Returns false if there was no branch to end.
+  */
+ bool
+ conditional_stack_pop(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(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 no branch state to set.
+  */
+ bool
+ conditional_stack_poke(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-blocks
+  */
+ bool
+ conditional_stack_empty(ConditionalStack cstack)
+ {
+ 	return cstack->head == NULL;
+ }
+ 
+ /*
+  * True if the current conditional block is active, or if there is no
+  * open \if (ie, we should execute commands normally)
+  */
+ bool
+ conditional_active(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
index ...96dbd7f .
*** a/src/bin/psql/conditional.h
--- b/src/bin/psql/conditional.h
***************
*** 0 ****
--- 1,62 ----
+ /*
+  * psql - the PostgreSQL interactive terminal
+  *
+  * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+  *
+  * src/bin/psql/conditional.h
+  */
+ #ifndef CONDITIONAL_H
+ #define CONDITIONAL_H
+ 
+ /*
+  * Possible states of a single level of \if block.
+  */
+ 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;
+ 
+ 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 void 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..2005b9a 100644
*** a/src/bin/psql/copy.c
--- b/src/bin/psql/copy.c
*************** handleCopyIn(PGconn *conn, FILE *copystr
*** 552,558 ****
  		/* interactive input probably silly, but give one prompt anyway */
  		if (showprompt)
  		{
! 			const char *prompt = get_prompt(PROMPT_COPY);
  
  			fputs(prompt, stdout);
  			fflush(stdout);
--- 552,558 ----
  		/* interactive input probably silly, but give one prompt anyway */
  		if (showprompt)
  		{
! 			const char *prompt = get_prompt(PROMPT_COPY, NULL);
  
  			fputs(prompt, stdout);
  			fflush(stdout);
*************** handleCopyIn(PGconn *conn, FILE *copystr
*** 590,596 ****
  
  			if (showprompt)
  			{
! 				const char *prompt = get_prompt(PROMPT_COPY);
  
  				fputs(prompt, stdout);
  				fflush(stdout);
--- 590,596 ----
  
  			if (showprompt)
  			{
! 				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
*************** slashUsage(unsigned short int pager)
*** 210,215 ****
--- 210,222 ----
  	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..47d6c50 100644
*** a/src/bin/psql/mainloop.c
--- b/src/bin/psql/mainloop.c
*************** const PsqlScanCallbacks psqlscan_callbac
*** 25,30 ****
--- 25,47 ----
  
  
  /*
+  * 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.
   *
*************** int
*** 35,40 ****
--- 52,58 ----
  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,
*************** MainLoop(FILE *source)
*** 69,74 ****
--- 86,92 ----
  
  	/* Create working state */
  	scan_state = psql_scan_create(&psqlscan_callbacks);
+ 	cond_stack = conditional_stack_create();
  
  	query_buf = createPQExpBuffer();
  	previous_buf = createPQExpBuffer();
*************** MainLoop(FILE *source)
*** 122,128 ****
--- 140,157 ----
  			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;
*************** MainLoop(FILE *source)
*** 140,146 ****
  			/* 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);
  		}
  		else
  		{
--- 169,176 ----
  			/* 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, cond_stack),
! 									query_buf);
  		}
  		else
  		{
*************** MainLoop(FILE *source)
*** 297,303 ****
  				}
  
  				/* execute query */
! 				success = SendQuery(query_buf->data);
  				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
  				pset.stmt_lineno = 1;
  
--- 327,333 ----
  				}
  
  				/* execute query */
! 				success = send_query(query_buf->data, cond_stack);
  				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
  				pset.stmt_lineno = 1;
  
*************** MainLoop(FILE *source)
*** 343,348 ****
--- 373,379 ----
  
  				/* execute backslash command */
  				slashCmdStatus = HandleSlashCmds(scan_state,
+ 												 cond_stack,
  												 query_buf->len > 0 ?
  												 query_buf : previous_buf);
  
*************** MainLoop(FILE *source)
*** 358,364 ****
  
  				if (slashCmdStatus == PSQL_CMD_SEND)
  				{
! 					success = SendQuery(query_buf->data);
  
  					/* transfer query to previous_buf by pointer-swapping */
  					{
--- 389,395 ----
  
  				if (slashCmdStatus == PSQL_CMD_SEND)
  				{
! 					success = send_query(query_buf->data, cond_stack);
  
  					/* transfer query to previous_buf by pointer-swapping */
  					{
*************** MainLoop(FILE *source)
*** 430,436 ****
  			pg_send_history(history_buf);
  
  		/* execute query */
! 		success = SendQuery(query_buf->data);
  
  		if (!success && die_on_error)
  			successResult = EXIT_USER;
--- 461,467 ----
  			pg_send_history(history_buf);
  
  		/* execute query */
! 		success = send_query(query_buf->data, cond_stack);
  
  		if (!success && die_on_error)
  			successResult = EXIT_USER;
*************** MainLoop(FILE *source)
*** 439,444 ****
--- 470,488 ----
  	}
  
  	/*
+ 	 * 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
*************** MainLoop(FILE *source)
*** 452,457 ****
--- 496,502 ----
  	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,72 ****
   */
  
  char *
! get_prompt(promptStatus_t status)
  {
  #define MAX_PROMPT_SIZE 256
  	static char destination[MAX_PROMPT_SIZE + 1];
--- 66,72 ----
   */
  
  char *
! get_prompt(promptStatus_t status, ConditionalStack cstack)
  {
  #define MAX_PROMPT_SIZE 256
  	static char destination[MAX_PROMPT_SIZE + 1];
*************** get_prompt(promptStatus_t status)
*** 188,194 ****
  					switch (status)
  					{
  						case PROMPT_READY:
! 							if (!pset.db)
  								buf[0] = '!';
  							else if (!pset.singleline)
  								buf[0] = '=';
--- 188,196 ----
  					switch (status)
  					{
  						case PROMPT_READY:
! 							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,16 ****
  
  /* enum promptStatus_t is now defined by psqlscan.h */
  #include "fe_utils/psqlscan.h"
  
! char	   *get_prompt(promptStatus_t status);
  
  #endif   /* PROMPT_H */
--- 10,17 ----
  
  /* enum promptStatus_t is now defined by psqlscan.h */
  #include "fe_utils/psqlscan.h"
+ #include "conditional.h"
  
! 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..353d6c8 100644
*** a/src/bin/psql/startup.c
--- b/src/bin/psql/startup.c
*************** main(int argc, char *argv[])
*** 331,349 ****
  			else if (cell->action == ACT_SINGLE_SLASH)
  			{
  				PsqlScanState scan_state;
  
  				if (pset.echo == PSQL_ECHO_ALL)
  					puts(cell->val);
  
  				scan_state = psql_scan_create(&psqlscan_callbacks);
  				psql_scan_setup(scan_state,
  								cell->val, strlen(cell->val),
  								pset.encoding, standard_strings());
  
! 				successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
  					? EXIT_SUCCESS : EXIT_FAILURE;
  
  				psql_scan_destroy(scan_state);
  			}
  			else if (cell->action == ACT_FILE)
  			{
--- 331,354 ----
  			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,
! 												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
index ...8180dab .
*** a/src/bin/psql/t/001_if.pl
--- b/src/bin/psql/t/001_if.pl
***************
*** 0 ****
--- 1,42 ----
+ use strict;
+ use warnings;
+ 
+ use Config;
+ use PostgresNode;
+ use TestLib;
+ use Test::More tests => 21;
+ 
+ #
+ # test that 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", '', 'reached EOF without finding 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 eb7f197..76a6d18 100644
*** a/src/test/regress/expected/psql.out
--- b/src/test/regress/expected/psql.out
*************** deallocate q;
*** 2735,2740 ****
--- 2735,2843 ----
  \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
+ -- 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..6d1a6b5 100644
*** a/src/test/regress/sql/psql.sql
--- b/src/test/regress/sql/psql.sql
*************** deallocate q;
*** 382,387 ****
--- 382,487 ----
  \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
+ 
+ -- 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

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 02:09  Robert Haas <robertmhaas@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Robert Haas @ 2017-03-12 02:09 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Mar 3, 2017 at 3:18 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
> I'm ok with this patch. I think that the very simple automaton code
> structure achieved is worth the very few code duplications. It is also
> significantly shorter than the nested if/switch variants, and it does
> exactly what Tom and Robert wished with respect to errors, so I think that
> this is a good compromise.

I think that I have not taken a firm position on what the behavior
should be with respect to errors.    I took the position that the
messages being printed saying what happened were too detailed, because
they not only described what had happened but also tried to
prognosticate what would happen next, which was dissimilar to what we
do elsewhere and likely to be hard to maintain - or even get right for
v1.  But I have not taken a position on what should happen if the
condition for \if or \elsif evaluates to a baffling value.  Corey's
prior proposal was to treat it, essentially, as neither true nor
false, skipping both arms of the if.  Tom seems to want an invalid
value treated as false.  You could also imagine pretending that the
command never happened at all, likely leading to complete chaos.
Other positions are also possible.  I suggest that doing it the way
Tom likes may be the path of least resistance, but this isn't really
something I'm very animated about personally.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 02:40  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-12 02:40 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Robert Haas <robertmhaas@gmail.com> writes:
> I think that I have not taken a firm position on what the behavior
> should be with respect to errors.    I took the position that the
> messages being printed saying what happened were too detailed, because
> they not only described what had happened but also tried to
> prognosticate what would happen next, which was dissimilar to what we
> do elsewhere and likely to be hard to maintain - or even get right for
> v1.

I thought the same of the version you were complaining about, but
the current patch seems to have dialed it back a good deal.  Do you
still find the current error messages unmaintainable?

> But I have not taken a position on what should happen if the
> condition for \if or \elsif evaluates to a baffling value.  Corey's
> prior proposal was to treat it, essentially, as neither true nor
> false, skipping both arms of the if.  Tom seems to want an invalid
> value treated as false.  You could also imagine pretending that the
> command never happened at all, likely leading to complete chaos.

Hmm, if that "prior proposal" was indeed on the table, I missed it.
The current patch, AFAICS, implements your third choice, which I quite
agree would lead to complete chaos; there would be no way to write a
script that did anything useful with that.

It is interesting to think about what would happen if "expr is neither
true nor false" were defined as "skip immediately to \endif" (which
I think is the natural generalization of what you said to apply to an
intermediate \elif).  I believe that it'd be possible to work with it,
but it's not very clear if it'd be easier or harder to work with than
the rule of treating bogus results as false.  What is clear is that
it'd be unlike any other conditional construct I ever worked with.
As was pointed out upthread, "treat error results as false" is what
you get from "if" in a POSIX shell.  I think it's fair also to draw
an analogy to what SQL does with null boolean values, which is to
treat them as false when a decision is required (in, eg, WHERE or
CASE).  So I think "treat bogus results as false" is the most
conservative, least likely to cause unhappy surprises, solution here.

> Other positions are also possible.

If you've got concrete ideas about that, let's hear them.  I'm not
trying to foreclose discussion.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 03:18  Robert Haas <robertmhaas@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 0 replies; 232+ messages in thread

From: Robert Haas @ 2017-03-12 03:18 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Mar 11, 2017 at 9:40 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I thought the same of the version you were complaining about, but
> the current patch seems to have dialed it back a good deal.  Do you
> still find the current error messages unmaintainable?

I haven't looked, but I had the impression this had been much improved.

>> But I have not taken a position on what should happen if the
>> condition for \if or \elsif evaluates to a baffling value.  Corey's
>> prior proposal was to treat it, essentially, as neither true nor
>> false, skipping both arms of the if.  Tom seems to want an invalid
>> value treated as false.  You could also imagine pretending that the
>> command never happened at all, likely leading to complete chaos.
>
> Hmm, if that "prior proposal" was indeed on the table, I missed it.
> The current patch, AFAICS, implements your third choice, which I quite
> agree would lead to complete chaos; there would be no way to write a
> script that did anything useful with that.

Well, other than: don't write a script with invalid commands in it.

But I'm not seriously advocating for that position.

> It is interesting to think about what would happen if "expr is neither
> true nor false" were defined as "skip immediately to \endif" (which
> I think is the natural generalization of what you said to apply to an
> intermediate \elif).  I believe that it'd be possible to work with it,
> but it's not very clear if it'd be easier or harder to work with than
> the rule of treating bogus results as false.  What is clear is that
> it'd be unlike any other conditional construct I ever worked with.

True.

> As was pointed out upthread, "treat error results as false" is what
> you get from "if" in a POSIX shell.  I think it's fair also to draw
> an analogy to what SQL does with null boolean values, which is to
> treat them as false when a decision is required (in, eg, WHERE or
> CASE).  So I think "treat bogus results as false" is the most
> conservative, least likely to cause unhappy surprises, solution here.

I don't mind that.  I was simply stating that I hadn't advocated for
anything in particular.

>> Other positions are also possible.
>
> If you've got concrete ideas about that, let's hear them.  I'm not
> trying to foreclose discussion.

I personally don't, but others may.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 06:52  David G. Johnston <david.g.johnston@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 232+ messages in thread

From: David G. Johnston @ 2017-03-12 06:52 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Mar 11, 2017 at 5:45 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

>
> * Whether or not you think it's important not to expand skipped variables,
> I think that it's critical that skipped backtick expressions not be
> executed.
> ​ [...] ​
> I do not think that a skipped \if or \elif
> should evaluate its argument at all.
>
>
​[...]
​

> * I'm not on board with having a bad expression result in failing
> the \if or \elif altogether.  It was stated several times upthread
> that that should be processed as though the result were "false",
> and I agree with that.


​+1​

​Oddly, Corey was using you as support for this position...though without
an actual quote:

"""
Tom was pretty adamant that invalid commands are not executed. So in a case
like this, with ON_ERROR_STOP off:

\if false
\echo 'a'
\elif true
\echo 'b'
\elif invalid
\echo 'c'
\endif

Both 'b' and 'c' should print, because "\elif invalid" should not execute.
The code I had before was simpler, but it missed that.
"""
https://www.postgresql.org/message-id/CADkLM%3De9BY_-
PT96mcs4qqiLtt8t-Fp%3De_AdycW-aS0OQvbC9Q%40mail.gmail.com

Also,

Robert made a comment somewhere along the line about users wanting to
simply re-type the intended line if the "invalid" was interactive and due
to a typo.  That concern is pretty much limited to just the "\if" situation
- if you typo an "\elif" block you can just type "\elif" again and begin
yet another "\elif" block.  I say we live with it and focus on the UX - if
you type \if no matter what happens after you hit enter you are in a
conditional block and will need to \endif at some point. Re-typing the
correct \if command will just make you need another one of them.

David J.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 07:35  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-12 07:35 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> Starting to poke at this... the proposal to add prove checks for psql 
> just to see whether \if respects ON_ERROR_STOP seems like an incredibly 
> expensive way to test a rather minor point. On my machine, "make check" 
> in bin/psql goes from zero time to close to 8 seconds.  I'm not really 
> on board with adding that kind of time to every buildfarm run for the 
> foreseeable future just for this.

ISTM that these tests allowed to find bugs in the implementation, so they 
were useful at some point. They are still useful in the short term if the 
implementation is to be changed significantly to respond to your various 
requirements. The underlying issue with TAP test is that it installs a new 
cluster on each script, which is quite costly.

In this case, the same result could be achieved with a number of small 
failing tests, which only launch "psql". Could that be acceptable? What 
you suggest is to keep only *one* failing test, which I find is kind of a 
regression from a testing coverage perspective, although obviously it is 
possible.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 08:22  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-12 08:22 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

> * Daniel Verite previously pointed out the desirability of disabling
> variable expansion while skipping script.  That doesn't seem to be here,

ISTM that it is still there, but for \elif conditions which are currently 
always checked.

   fabien=# \if false
   fabien@#   \echo `echo BAD`
     command ignored, use \endif or Ctrl-C to exit current branch.
   fabien@# \else
   fabien=#   \echo `echo OK`
     OK
   fabien=# \endif


> IIRC, I objected to putting knowledge of ConditionalStack into the 
> shared psqlscan.l lexer, and I still think that would be a bad idea; but 
> we need some way to get the lexer to shut that off. Probably the best 
> way is to add a passthrough "void *" argument that would let the 
> get_variable callback function mechanize the rule about not expanding in 
> a false branch.

Hmmm. I see this as a circumvolute way of providing the stack knowledge 
without actually giving the stack... it seems that would work, so why not.

> * Whether or not you think it's important not to expand skipped variables,
> I think that it's critical that skipped backtick expressions not be
> executed.

> 	\if something
> 	\elif `expr :var1 + :var2 = :var3`
> 	\endif

> I think it's essential that expr not be called if the first if-condition
> succeeded.

This was the behavior at some point, but it was changed because we 
understood that it was required that boolean errors were detected and the 
resulting command be simply ignored. I'm really fine with having that 
back.

> * The documentation says that an \if or \elif expression extends to the
> end of the line, but actually the code is just eating one OT_NORMAL
> argument.  That means it's OK to do this: [...]

> 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 :-(.

Indeed.

IMO the very versatile lexing conventions of backslash commands, or rather 
their actual lack of any consistency, makes it hard to get something very 
sane out of this, especially with the "do not evaluate in false branch" 
argument.

As a simple way out, I suggest to:

(1) document that \if-related commands MUST be on their own
     line (i.e. like cpp #if directives?).

(2) check that it is indeed the case when one \if-related
     command detected.

(3) call it a feature if someone does not follow the rule and gets a
     strange behavior as a result, as below:

> regression=# \if 0
> regression@# \echo foo \endif
>   command ignored, use \endif or Ctrl-C to exit current branch.
>   (notice we're not out of the conditional)


> * I'm not on board with having a bad expression result in failing
> the \if or \elif altogether.

This was understood as a requirement on previous versions which did not 
fail. I do agree that it seems better to keep the structure on errors, at 
least for script usage.

> It was stated several times upthread that that should be processed as 
> though the result were "false", and I agree with that.

I'm fine with that, if everyone could agree before Corey spends more time 
on this...

> [...] We might as well replace the recommendation to use ON_ERROR_STOP 
> with a forced abort() for an invalid expression value, because trying to 
> continue a script with this behavior is entirely useless.

Hmmm. Maybe your remark is rhetorical. That could be for scripting use, 
but in interactive mode aborting coldly on syntax errors is not too nice 
for the user.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 17:12  Corey Huinker <corey.huinker@gmail.com>
  parent: David G. Johnston <david.g.johnston@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-12 17:12 UTC (permalink / raw)
  To: David G. Johnston <david.g.johnston@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sun, Mar 12, 2017 at 1:52 AM, David G. Johnston <
david.g.johnston@gmail.com> wrote:
>
> Oddly, Corey was using you as support for this position...though without
an actual quote:
>
> """

Reading this, I started to wonder "so how did I get that impression?" and I
found this from Feb 9:

IMO, an erroneous backslash command should have no effect, period.
"It failed but we'll treat it as if it were valid" is a rathole
I don't want to descend into.  It's particularly bad in interactive
mode, because the most natural thing to do is correct your spelling
and issue the command again --- but if psql already decided to do
something on the strength of the mistaken command, that doesn't work,
and you'll have to do something or other to unwind the unwanted
control state before you can get back to what you meant to do.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 17:24  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-12 17:24 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> Reading this, I started to wonder "so how did I get that impression?" and I
> found this from Feb 9:

> IMO, an erroneous backslash command should have no effect, period.
> "It failed but we'll treat it as if it were valid" is a rathole
> I don't want to descend into.  It's particularly bad in interactive
> mode, because the most natural thing to do is correct your spelling
> and issue the command again --- but if psql already decided to do
> something on the strength of the mistaken command, that doesn't work,
> and you'll have to do something or other to unwind the unwanted
> control state before you can get back to what you meant to do.

Yeah, it's not the greatest thing for interactive usage, but as we
already discussed, this feature needs to be optimized for scripting not
interaction --- and even a bit of thought shows that the current behavior
is disastrous for scripting.  If your only suggestion for getting sane
behavior in a script is "set ON_ERROR_STOP", you've failed to provide
useful error handling.

One point here is that we need to distinguish problems in the expression,
which could arise from changing variable values, from some other types of
mistakes like \elif with no preceding \if.  When you see something like
that you pretty much have to treat it as a no-op; but I don't think that's
a problem for scripting usage.

We could imagine resolving this tension by treating failed \if expressions
differently in interactive and noninteractive cases.  But I fear that cure
would be worse than the disease.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 17:40  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-12 17:40 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> (1) document that \if-related commands MUST be on their own
>     line (i.e. like cpp #if directives?).
>

I have no opinion on whether \if-related comments must be on their own
line, though I coded as if that were the case.

I want to point out that the goal down the road is to allow rudimentary
expressions beyond just 'will this string cast to boolean true'.

For example, in the earlier thread "Undefined psql variables", I proposed a
slash command that would test if a named psql var were defined, and if not
then assign it a value.

Tom suggested leveraging if-then infrastructure like this

     \if not defined(x)
      \set x y
     \fi

Which would be great. I ask that whatever we decide in terms of how much
more input we read to digest the expression allow for constructs like the
one above.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 17:45  David G. Johnston <david.g.johnston@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: David G. Johnston @ 2017-03-12 17:45 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sun, Mar 12, 2017 at 10:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

>
> One point here is that we need to distinguish problems in the expression,
> which could arise from changing variable values, from some other types of
> mistakes like \elif with no preceding \if.  When you see something like
> that you pretty much have to treat it as a no-op; but I don't think that's
> a problem for scripting usage.
>

​One of my discarded write-ups from last night made a point that we don't
really distinguish between run-time and compile-time errors - possibly
because we haven't had to until now.

​If we detect what would be considered a compile-time error (\elif after
\else for instance) we could treat anything that isn't a conditional
meta-command as a no-op with a warning (and exit in stop-script mode)​.

There are only four commands and a finite number of usage permutations.
Enumerating and figuring out the proper behavior for each should be done.

Thus - ​If the expressions are bad they are considered false but the block
is created

If the flow-control command is bad the system will tell the user why and
how to get back to a valid state - the entire machine state goes INVALID
until a corrective command is encountered.

For instance:

\if
\else
\elif
warning: elif block cannot occur directly within an \else block.  either
start a new \if, \endif the current scope, or type \else to continue
entering commands into the existing else block.  no expression evaluation
has occurred.
\echo 'c'
warning: command ignored in broken \if block scope - see prior correction
options

Yes, that's wordy, but if that was shown the user would be able to
recognize their situation and be able to get back to their desired state.

Figuring out what the valid correction commands are for each invalid state,
and where the user is left, is tedious but mechanical.

So we'd need an INVALID state as well as the existing IGNORE state.

\endif would always work - but take you up one nesting level

The user shouldn't need to memorize the invalid state rules.  While we
could document them and point the reader there having them inline seems
preferable.
​

>
> We could imagine resolving this tension by treating failed \if expressions
> differently in interactive and noninteractive cases.  But I fear that cure
> would be worse than the disease.
>

​I don't think this becomes necessary - we should distinguish the error
types the same in both modes.​

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-12 19:29  Tom Lane <tgl@sss.pgh.pa.us>
  parent: David G. Johnston <david.g.johnston@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-12 19:29 UTC (permalink / raw)
  To: David G. Johnston <david.g.johnston@gmail.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

"David G. Johnston" <david.g.johnston@gmail.com> writes:
> There are only four commands and a finite number of usage permutations.
> Enumerating and figuring out the proper behavior for each should be done.

> Thus - ​If the expressions are bad they are considered false but the block
> is created

> If the flow-control command is bad the system will tell the user why and
> how to get back to a valid state - the entire machine state goes INVALID
> until a corrective command is encountered.

> For instance:

> \if
> \else
> \elif
> warning: elif block cannot occur directly within an \else block.  either
> start a new \if, \endif the current scope, or type \else to continue
> entering commands into the existing else block.  no expression evaluation
> has occurred.
> \echo 'c'
> warning: command ignored in broken \if block scope - see prior correction
> options

This is looking a whole lot like the overcomplicated error reporting that
we already considered and rejected.  I think it's sufficient to print
something like "\elif is not allowed to follow \else; command ignored"
and not change state.  We're not really helping anybody by going into
an "invalid machine state" AFAICS, and having such a thing complicates
the mental model more than I'd like.

A different way of looking at this problem, which will seem like overkill
right now but would absolutely not be once you consider looping, is that
what should happen when we see \if is that we do nothing but absorb text
until we see the matching \endif.  At that point we could bitch and throw
everything away if, say, there's \elif after \else, or anything else you
want to regard as a "compile time error".  Otherwise we start execution,
and from there on it probably has to behave as we've been discussing.
But this'd be pretty unfriendly from an interactive standpoint, and I'm
not really convinced that it makes for significantly better error
reporting.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-13 15:18  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-13 15:18 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers

I wrote:
> IIRC, I objected to putting knowledge of ConditionalStack
> into the shared psqlscan.l lexer, and I still think that would be a bad
> idea; but we need some way to get the lexer to shut that off.  Probably
> the best way is to add a passthrough "void *" argument that would let the
> get_variable callback function mechanize the rule about not expanding
> in a false branch.

Here's a proposed patch that adds a passthrough of this sort.

The passthrough argument is passed only to the get_variable callback.
I dithered about whether to also pass it to the write_error callback,
but ultimately decided not to for now.  Neither psql nor pgbench wants it,
and in the case of psql we'd have to invent a separate wrapper function
because we would certainly not want to change the signature of
psql_error().

Barring objection I'll push this so that Corey can rebase over it.

			regards, tom lane

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-diff] psql-lexer-callback-passthrough.patch (6.5K, ../../10108.1489418309@sss.pgh.pa.us/2-psql-lexer-callback-passthrough.patch)
  download | inline diff:
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 1aa56ab..e9d4fe6 100644
*** a/src/bin/psql/common.c
--- b/src/bin/psql/common.c
*************** setQFout(const char *fname)
*** 119,127 ****
   * 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.)
   */
  char *
! psql_get_variable(const char *varname, bool escape, bool as_ident)
  {
  	char	   *result;
  	const char *value;
--- 119,131 ----
   * 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.)
+  *
+  * "passthrough" is the pointer previously given to psql_scan_set_passthrough.
+  * psql currently doesn't use this.
   */
  char *
! psql_get_variable(const char *varname, bool escape, bool as_ident,
! 				  void *passthrough)
  {
  	char	   *result;
  	const char *value;
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index a83bc69..3d8b8da 100644
*** a/src/bin/psql/common.h
--- b/src/bin/psql/common.h
***************
*** 16,22 ****
  extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
  extern bool setQFout(const char *fname);
  
! extern char *psql_get_variable(const char *varname, bool escape, bool as_ident);
  
  extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);
  
--- 16,23 ----
  extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
  extern bool setQFout(const char *fname);
  
! extern char *psql_get_variable(const char *varname, bool escape, bool as_ident,
! 				  void *passthrough);
  
  extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);
  
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 5b7953b..ba4a08d 100644
*** a/src/bin/psql/psqlscanslash.l
--- b/src/bin/psql/psqlscanslash.l
*************** other			.
*** 243,249 ****
  															 yyleng - 1);
  						value = cur_state->callbacks->get_variable(varname,
  																   false,
! 																   false);
  						free(varname);
  
  						/*
--- 243,250 ----
  															 yyleng - 1);
  						value = cur_state->callbacks->get_variable(varname,
  																   false,
! 																   false,
! 																   cur_state->cb_passthrough);
  						free(varname);
  
  						/*
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..19b3e57 100644
*** a/src/fe_utils/psqlscan.l
--- b/src/fe_utils/psqlscan.l
*************** other			.
*** 700,706 ****
  					if (cur_state->callbacks->get_variable)
  						value = cur_state->callbacks->get_variable(varname,
  																   false,
! 																   false);
  					else
  						value = NULL;
  
--- 700,707 ----
  					if (cur_state->callbacks->get_variable)
  						value = cur_state->callbacks->get_variable(varname,
  																   false,
! 																   false,
! 																   cur_state->cb_passthrough);
  					else
  						value = NULL;
  
*************** psql_scan_destroy(PsqlScanState state)
*** 923,928 ****
--- 924,942 ----
  }
  
  /*
+  * Set the callback passthrough pointer for the lexer.
+  *
+  * This could have been integrated into psql_scan_create, but keeping it
+  * separate allows the application to change the pointer later, which might
+  * be useful.
+  */
+ void
+ psql_scan_set_passthrough(PsqlScanState state, void *passthrough)
+ {
+ 	state->cb_passthrough = passthrough;
+ }
+ 
+ /*
   * Set up to perform lexing of the given input line.
   *
   * The text at *line, extending for line_len bytes, will be scanned by
*************** psqlscan_escape_variable(PsqlScanState s
*** 1409,1415 ****
  	/* Variable lookup. */
  	varname = psqlscan_extract_substring(state, txt + 2, len - 3);
  	if (state->callbacks->get_variable)
! 		value = state->callbacks->get_variable(varname, true, as_ident);
  	else
  		value = NULL;
  	free(varname);
--- 1423,1430 ----
  	/* Variable lookup. */
  	varname = psqlscan_extract_substring(state, txt + 2, len - 3);
  	if (state->callbacks->get_variable)
! 		value = state->callbacks->get_variable(varname, true, as_ident,
! 											   state->cb_passthrough);
  	else
  		value = NULL;
  	free(varname);
diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h
index 21c4f22..0cc632b 100644
*** a/src/include/fe_utils/psqlscan.h
--- b/src/include/fe_utils/psqlscan.h
*************** typedef struct PsqlScanCallbacks
*** 53,59 ****
  {
  	/* Fetch value of a variable, as a pfree'able string; NULL if unknown */
  	/* This pointer can be NULL if no variable substitution is wanted */
! 	char	   *(*get_variable) (const char *varname, bool escape, bool as_ident);
  	/* Print an error message someplace appropriate */
  	/* (very old gcc versions don't support attributes on function pointers) */
  #if defined(__GNUC__) && __GNUC__ < 4
--- 53,60 ----
  {
  	/* Fetch value of a variable, as a pfree'able string; NULL if unknown */
  	/* This pointer can be NULL if no variable substitution is wanted */
! 	char	   *(*get_variable) (const char *varname, bool escape,
! 										   bool as_ident, void *passthrough);
  	/* Print an error message someplace appropriate */
  	/* (very old gcc versions don't support attributes on function pointers) */
  #if defined(__GNUC__) && __GNUC__ < 4
*************** typedef struct PsqlScanCallbacks
*** 67,72 ****
--- 68,75 ----
  extern PsqlScanState psql_scan_create(const PsqlScanCallbacks *callbacks);
  extern void psql_scan_destroy(PsqlScanState state);
  
+ extern void psql_scan_set_passthrough(PsqlScanState state, void *passthrough);
+ 
  extern void psql_scan_setup(PsqlScanState state,
  				const char *line, int line_len,
  				int encoding, bool std_strings);
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..b4044e8 100644
*** a/src/include/fe_utils/psqlscan_int.h
--- b/src/include/fe_utils/psqlscan_int.h
*************** typedef struct PsqlScanStateData
*** 115,123 ****
  	char	   *dolqstart;		/* current $foo$ quote start string */
  
  	/*
! 	 * Callback functions provided by the program making use of the lexer.
  	 */
  	const PsqlScanCallbacks *callbacks;
  } PsqlScanStateData;
  
  
--- 115,125 ----
  	char	   *dolqstart;		/* current $foo$ quote start string */
  
  	/*
! 	 * Callback functions provided by the program making use of the lexer,
! 	 * plus a void* callback passthrough argument.
  	 */
  	const PsqlScanCallbacks *callbacks;
+ 	void	   *cb_passthrough;
  } PsqlScanStateData;
  
  

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-13 16:36  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-13 16:36 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers

>
>
> Barring objection I'll push this so that Corey can rebase over it.
>
>                         regards, tom lane
>

Seems straightforward, and I appreciate you doing it for me!

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-13 19:11  Daniel Verite <daniel@manitou-mail.org>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-03-13 19:11 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

	Tom Lane wrote:

> when we see \if is that we do nothing but absorb text
> until we see the matching \endif.  At that point we could bitch and throw
> everything away if, say, there's \elif after \else, or anything else you
> want to regard as a "compile time error".  Otherwise we start execution,
> and from there on it probably has to behave as we've been discussing.
> But this'd be pretty unfriendly from an interactive standpoint, and I'm
> not really convinced that it makes for significantly better error
> reporting.

This is basically what bash does. In an if/else/fi block
in an interactive session, the second prompt is displayed at every new
line and nothing gets executed until it recognizes the end of the
block and it's valid as a whole. Otherwise, nothing of the block
gets executed. That doesn't strike me as unfriendly.

When non-interactive, in addition to the block not being executed,
the fact that it fails implies that the execution of the current script
is ended, independently of the errexit setting.
If errexit is set, the interpreter terminates. If it was
an included script and errexit is not set, the execution resumes
after the point of the inclusion.

On the whole, isn't that a reasonable model to follow for psql?

Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-13 21:15  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-13 21:15 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers

Corey Huinker <corey.huinker@gmail.com> writes:
>> Barring objection I'll push this so that Corey can rebase over it.

> Seems straightforward, and I appreciate you doing it for me!

Hearing no objections, pushed.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-13 21:21  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-13 21:21 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

"Daniel Verite" <daniel@manitou-mail.org> writes:
> Tom Lane wrote:
>> when we see \if is that we do nothing but absorb text
>> until we see the matching \endif.  At that point we could bitch and throw
>> everything away if, say, there's \elif after \else, or anything else you
>> want to regard as a "compile time error".  Otherwise we start execution,
>> and from there on it probably has to behave as we've been discussing.
>> But this'd be pretty unfriendly from an interactive standpoint, and I'm
>> not really convinced that it makes for significantly better error
>> reporting.

> On the whole, isn't that a reasonable model to follow for psql?

One thing that occurs to me after more thought is that with such a model,
we could not have different lexing rules for live vs not-live branches,
since we would not have made those decisions before scanning the input.
This seems problematic.  Even if you discount the question of whether
variable expansion is allowed to change command-boundary decisions, we'd
still not want backtick execution to happen everywhere in the block, ISTM.

Maybe we could fix things so that backtick execution happens later, but
it would be a pretty significant and invasive change to backslash command
execution, I'm afraid.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-16 20:57  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-16 20:57 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Mar 13, 2017 at 5:21 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> "Daniel Verite" <daniel@manitou-mail.org> writes:
> > Tom Lane wrote:
> >> when we see \if is that we do nothing but absorb text
> >> until we see the matching \endif.  At that point we could bitch and
> throw
> >> everything away if, say, there's \elif after \else, or anything else you
> >> want to regard as a "compile time error".  Otherwise we start execution,
> >> and from there on it probably has to behave as we've been discussing.
> >> But this'd be pretty unfriendly from an interactive standpoint, and I'm
> >> not really convinced that it makes for significantly better error
> >> reporting.
>
> > On the whole, isn't that a reasonable model to follow for psql?
>
> One thing that occurs to me after more thought is that with such a model,
> we could not have different lexing rules for live vs not-live branches,
> since we would not have made those decisions before scanning the input.
> This seems problematic.  Even if you discount the question of whether
> variable expansion is allowed to change command-boundary decisions, we'd
> still not want backtick execution to happen everywhere in the block, ISTM.
>
> Maybe we could fix things so that backtick execution happens later, but
> it would be a pretty significant and invasive change to backslash command
> execution, I'm afraid.
>
>                         regards, tom lane
>

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.

1. Convert perl tests to a single regular regression test.

2. Have MainLoop() pass the cond_stack to the lexer via
    psql_scan_set_passthrough(scan_state, (void *) cond_stack);

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.

4. Change variable expansion and backtick execution in false branches to
match new policy.

I've inferred that current preference would be for no expansion and no
execution.

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.

Did I miss anything?

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-16 21:14  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-16 21:14 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@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


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 01:28  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-17 01:28 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

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 <tgl@sss.pgh.pa.us> wrote:

> Corey Huinker <corey.huinker@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

Attachments:

  [text/plain] 0001.if_endif.v23.diff (28.4K, ../../CADkLM=fHrBW4Z+ixPD35Ad+4oKG1KX0UK7xDY+0G9gdNnDQS8A@mail.gmail.com/3-0001.if_endif.v23.diff)
  download | inline diff:
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


^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 06:18  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-17 06:18 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey & Tom,

> What is not done:
> - 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.

ISTM that I've tried to suggest to work around that complexity by:
  - document that \if-related commands should only occur at line start
    (and extend to eol).
  - detect and complain when this is not the case.
  - if some border cases are not detected, call it a feature.

ISTM that Tom did not respond to this possibly simpler approach... Maybe a 
"no" would be enough before starting heavy work which would touch all 
other commands...

Tom?

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 10:03  Erik Rijkers <er@xs4all.nl>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Erik Rijkers @ 2017-03-17 10:03 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On 2017-03-17 02:28, Corey Huinker wrote:
> Attached is the latest work. Not everything is done yet. I post it 
> because

> 0001.if_endif.v23.diff


This patch does not compile for me (gcc 6.3.0):

command.c:38:25: fatal error: conditional.h: No such file or directory
  #include "conditional.h"
                          ^
compilation terminated.
make[3]: *** [command.o] Error 1
make[2]: *** [all-psql-recurse] Error 2
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [all-bin-recurse] Error 2
make: *** [all-src-recurse] Error 2

Perhaps that is expected, as "Not everything is done yet",  but I can't 
tell from your email so I thought I'd report ir anyway. Ignore as 
appropriate...


Thanks,

Erik Rijkers


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 11:33  Daniel Verite <daniel@manitou-mail.org>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-03-17 11:33 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

	Tom Lane wrote:

> OT_WHOLE_LINE is not what you want because that results in verbatim
> copying, without variable expansion or anything

But if we want to implement "\if defined :foo" in the future
isn't it just what we need?

Also we could leave open the option to accept an SQL expression
here. I expect people will need SQL as the evaluator in a lot of cases.
So far we need to do that:

  SELECT sql_expr ... AS varname \gset
  \if :varname
  ...
  \endif

Surely users will wonder right away why they can't write it like this
instead:

  \if (sql_expr)
  ...
  \endif

There's a precedent with \copy accepting a query inside parentheses,
using OT_WHOLE_LINE.


Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 13:33  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-17 13:33 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Corey Huinker <corey.huinker@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

"Daniel Verite" <daniel@manitou-mail.org> writes:
> 	Tom Lane wrote:
>> OT_WHOLE_LINE is not what you want because that results in verbatim
>> copying, without variable expansion or anything

> But if we want to implement "\if defined :foo" in the future
> isn't it just what we need?

I don't think that should mean what you think.  I believe an appropriate
spelling of what you mean is "\if defined foo".  What you wrote should
result in foo being expanded and then a defined-ness test being performed
on whatever variable name results.

> Also we could leave open the option to accept an SQL expression
> here. I expect people will need SQL as the evaluator in a lot of cases.

Right, and they'll also want to insert variable references into that
SQL.  In the short term though, `expr ...` is going to be the solution,
and that means we'd better not throw away the behavior of expanding
back-ticks.

> There's a precedent with \copy accepting a query inside parentheses,
> using OT_WHOLE_LINE.

IMV, \copy is just about completely broken in this regard, precisely
because it fails to expand variable references.  I don't want to
emulate that brain-damage for \if.  (I believe, btw, that part
of the reason for \copy behaving this way is that we wanted to
preserve an ancient behavior whereby Windows users were not forced
to double backslashes in \windows\style\path\names.  Fortunately,
that bit of silliness need not be considered for \if.)

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 14:37  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-17 14:37 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Fabien COELHO <coelho@cri.ensmp.fr> writes:
> ISTM that I've tried to suggest to work around that complexity by:
>   - document that \if-related commands should only occur at line start
>     (and extend to eol).
>   - detect and complain when this is not the case.

I think this is a lousy definition, and would never be considered if we
were working in a green field.  Moreover, preventing such cases would be
pretty darn ugly/messy as well.

I also fear that there are corner cases where the behavior would still
be inconsistent.  Consider

	\if ...
	\set foo `echo \endif should not appear here`

If the \if succeeds, the result of the second line would be to set foo
to "endif should not appear here" (and we'd remain in the \if block).
But if the \if fails and we need to skip the \set command, any approach
that involves changing the argument parsing rules will fail to recognize
the backtick construct, and then will see the \endif as a command.
Similar examples can be constructed using \copy.

It's possible that we could keep the implementation that uses an early exit
from exec_command() if we were to move argument collection for all
backslash commands up to the start of the function.  It would still be
a bit invasive, but perhaps not too awful: I'm imagining that instead of

    else if (strcmp(cmd, "setenv") == 0)
    {
        char       *envvar = psql_scan_slash_option(scan_state,
                                                    OT_NORMAL, NULL, false);
        char       *envval = psql_scan_slash_option(scan_state,
                                                    OT_NORMAL, NULL, false);

we'd write

    else if (strcmp(cmd, "setenv") == 0)
    {
        char       *envvar = args[0];
        char       *envval = args[1];

where the args array had been filled at the top of the function.
The top-of-function code would have to know all the cases where
commands didn't use basic OT_NORMAL processing, but there aren't
that many of those, I think.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 15:32  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-17 15:32 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

>> ISTM that I've tried to suggest to work around that complexity by:
>>   - document that \if-related commands should only occur at line start
>>     (and extend to eol).
>>   - detect and complain when this is not the case.
>
> I think this is a lousy definition, and would never be considered if we
> were working in a green field.

Yes, sure. As you pointed out, the field is not green: there is no clean 
lexical convention, too bad. I'm trying to deal with that without too much 
fuss in the code.

> Moreover, preventing such cases would be pretty darn ugly/messy as well.
>
> I also fear that there are corner cases where the behavior would still
> be inconsistent.  Consider
>
> 	\if ...
> 	\set foo `echo \endif should not appear here`

In this instance, ISTM that there is no problem. On "\if true", set is 
executed, all is well. On "\if false", the whole line would be skipped 
because the if-related commands are only expected on their own line, all 
is well again. No problem.

Another more interesting one would be:

   \if ...
     \unset foo \endif

On true, unset get its argument, then endif is detected as a backslash 
command, but it would see that it is not on its own line, so it would 
error out *and* be ignored. On false, the whole line would be ignored, it 
would just not complain, but it would be the same, i.e. it is *not* an 
\endif again. The drawback is only that the wrong \endif is not detected 
when under a false branch. That is why I added a third bullet "call border 
cases a feature".

ISTM that the proposed simple rules allow to deal with the situation 
without having to dive into each command lexing rules, and changing the 
existing code significantly. The drawback is that misplaced \endif are not 
detected in false branch, but they are ignored anyway, which is fine.

> I'm imagining that instead of
>
> [...] char       *envvar = psql_scan_slash_option(scan_state,
>
> we'd write
>
> [...] char       *envvar = args[0];
>
> where the args array had been filled at the top of the function.
> The top-of-function code would have to know all the cases where
> commands didn't use basic OT_NORMAL processing, but there aren't
> that many of those, I think.

Yep, I understood the idea. There are a few of those, about 49 OT_* in 
"command.c", including 34 OT_NORMAL, 1 OT_NO_EVAL, 3 OT_FILEPIPE, 9 
OT_WHOLELINE, some OT_SQLHACKID & OT_SQLID. I'm not sure of the 
combinations.

It still means splitting command lexing knowledge in several places. I'm 
not convinced by the impact on the resulting code with regard to 
readability and maintainability, so if there could be a way to get 
something without taking that path that would be nice, hence my 
suggestions.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 15:40  Corey Huinker <corey.huinker@gmail.com>
  parent: Erik Rijkers <er@xs4all.nl>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-17 15:40 UTC (permalink / raw)
  To: Erik Rijkers <er@xs4all.nl>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Greg Stark <stark@mit.edu>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> command.c:38:25: fatal error: conditional.h: No such file or directory
>  #include "conditional.h"
>

Odd, it's listed as a new file in git status. Anyway, my point of posting
the WIP patch was to give people a reference point and spark discussion
about the next step, and it succeeded at that.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 15:42  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-17 15:42 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> I also fear that there are corner cases where the behavior would still
>> be inconsistent.  Consider
>> 
>> \if ...
>> \set foo `echo \endif should not appear here`

> In this instance, ISTM that there is no problem. On "\if true", set is 
> executed, all is well. On "\if false", the whole line would be skipped 
> because the if-related commands are only expected on their own line, all 
> is well again. No problem.

AFAICS, you misunderstood the example completely, or else you're proposing
syntax restrictions that are even more bizarre and unintelligible than
I thought before.  We cannot have a situation where the syntax rules for
backslash commands inside an \if are fundamentally different from what
they are elsewhere; that's just going to lead to confusion and bug
reports.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 16:03  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-17 16:03 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Mar 17, 2017 at 11:42 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Fabien COELHO <coelho@cri.ensmp.fr> writes:
> >> I also fear that there are corner cases where the behavior would still
> >> be inconsistent.  Consider
> >>
> >> \if ...
> >> \set foo `echo \endif should not appear here`
>
> > In this instance, ISTM that there is no problem. On "\if true", set is
> > executed, all is well. On "\if false", the whole line would be skipped
> > because the if-related commands are only expected on their own line, all
> > is well again. No problem.
>
> AFAICS, you misunderstood the example completely, or else you're proposing
> syntax restrictions that are even more bizarre and unintelligible than
> I thought before.  We cannot have a situation where the syntax rules for
> backslash commands inside an \if are fundamentally different from what
> they are elsewhere; that's just going to lead to confusion and bug
> reports.
>
>                         regards, tom lane
>

I think Fabien was arguing that inside a false block there would be no
syntax rules beyond "is the first non-space character on this line a '\'
and if so is it followed with a if/elif/else/endif?". If the answer is no,
skip the line. To me that seems somewhat similar to Tom's suggestion that a
false branch just keeps consuming text until it encounters a \conditional
or EOF.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 16:33  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-17 16:33 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
> I think Fabien was arguing that inside a false block there would be no
> syntax rules beyond "is the first non-space character on this line a '\'
> and if so is it followed with a if/elif/else/endif?". If the answer is no,
> skip the line. To me that seems somewhat similar to Tom's suggestion that a
> false branch just keeps consuming text until it encounters a \conditional
> or EOF.

Hmm.  If we can keep the syntax requirements down to "\if and friends
must be the first backslash command on the line", and not change the
apparent behavior for any other command type, it probably would be okay
from the user's standpoint.  I'm not really convinced that this approach
will accomplish that, though, and especially not that it will do so
without injecting some ugliness into the core lexer.

In the end, I suspect that teaching all the backslash commands to do
nothing after absorbing their arguments is likely to be the least messy
way to tackle this, even if it makes for a rather bulky patch.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 16:42  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-17 16:42 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

>>> I also fear that there are corner cases where the behavior would still
>>> be inconsistent.  Consider
>>>
>>> \if ...
>>> \set foo `echo \endif should not appear here`
>
>> In this instance, ISTM that there is no problem. On "\if true", set is
>> executed, all is well. On "\if false", the whole line would be skipped
>> because the if-related commands are only expected on their own line, all
>> is well again. No problem.
>
> AFAICS, you misunderstood the example completely, or else you're proposing
> syntax restrictions that are even more bizarre and unintelligible than
> I thought before.

Hmmm. The example you put forward does work as expected with the rule I 
suggested. It does not prove that the rules are good or sane, I'm just 
stating that the example would work consistently.

> We cannot have a situation where the syntax rules for backslash commands 
> inside an \if are fundamentally different from what they are elsewhere;

Indeed, I do not see an issue with requiring some new backslash commands 
to be on their own line: Any average programmer would put them like that 
anyway for readability. What is the point of trying to write code to 
handle strange unmaintainable oneliners?

> that's just going to lead to confusion and bug reports.

Whatever is done, there will be some confusion and bug reports:-)

If someone writes a strange one-liner and see that it generates errors, 
then the error messages should be clear enough. Maybe they will complain 
and fill in bugs because they like backslash-command oneliners. That is 
life.

Now you are the committer and Corey is the developer. I'm just a reviewer 
trying to help. I can still review a larger patch which tries to be subtly 
compatible with a lack of previous clear design by adding code complexity, 
even if I think that this particular effort is a bad idea (i.e. mis-spent 
resource on a useless sub-feature which makes future maintenance harder). 
With some luck, Corey may find a way of doing it which is not too bad.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 16:56  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-17 16:56 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> In the end, I suspect that teaching all the backslash commands to do
> nothing after absorbing their arguments is likely to be the least messy
> way to tackle this, even if it makes for a rather bulky patch.
>
>
Perhaps, but just glancing at \connect makes me think that for some
commands (present or future) the number of args might depend on the value
of the first arg, and variable expansion-or-not, backtick execution-or-not
could alter the number of apparent args on the line, like this:

\set x 'arg1 arg2'

\if false
    \cmd_that_takes_exactly_two_args :x
\endif

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 17:27  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-17 17:27 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
>> In the end, I suspect that teaching all the backslash commands to do
>> nothing after absorbing their arguments is likely to be the least messy
>> way to tackle this, even if it makes for a rather bulky patch.

> Perhaps, but just glancing at \connect makes me think that for some
> commands (present or future) the number of args might depend on the value
> of the first arg, and variable expansion-or-not, backtick execution-or-not
> could alter the number of apparent args on the line, like this:

> \set x 'arg1 arg2'

> \if false
>     \cmd_that_takes_exactly_two_args :x
> \endif

Yeah, throwing errors for bad arguments would also need to be suppressed.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-17 18:18  Corey Huinker <corey.huinker@gmail.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-17 18:18 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> > \set x 'arg1 arg2'
>
> > \if false
> >     \cmd_that_takes_exactly_two_args :x
> > \endif
>
> Yeah, throwing errors for bad arguments would also need to be suppressed.
>
>                         regards, tom lane
>

Ok, barring other feedback, I'm going to take my marching orders as "make
each slash command active-aware". To keep that sane, I'm probably going to
break out each slash command family into it's own static function.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-18 17:13  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-18 17:13 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Mar 17, 2017 at 2:18 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

>
>> > \set x 'arg1 arg2'
>>
>> > \if false
>> >     \cmd_that_takes_exactly_two_args :x
>> > \endif
>>
>> Yeah, throwing errors for bad arguments would also need to be suppressed.
>>
>>                         regards, tom lane
>>
>
> Ok, barring other feedback, I'm going to take my marching orders as "make
> each slash command active-aware". To keep that sane, I'm probably going to
> break out each slash command family into it's own static function.
>

...and here it is.

v24 highlights:

- finally using git format-patch
- all conditional slash commands broken out into their own functions
(exec_command_$NAME) , each one tests if it's in an active branch, and if
it's not it consumes the same number of parameters, but discards them.
comments for each slash-command family were left as-is, may need to be
bulked up.
- TAP tests discarded in favor of one ON_EROR_STOP test for invalid \elif
placement
- documentation recommending ON_ERROR_STOP removed, because invalid
expressions no longer throw off if-endif balance
- documentation updated to reflex that contextually-correct-but-invalid
boolean expressions are treated as false
- psql_get_variable has a passthrough void pointer now, but I ended up not
needing it. Instead, all slash commands in false blocks either fetch
OT_NO_EVAL or OT_WHOLE_LINE options. If I'm missing something, let me know.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001.if_endif.v24.patch (88.5K, ../../CADkLM=f3_Sf2EHNBFgLWUA-1PRSJ-qVmyVqFuHjXEgLPWjaJtw@mail.gmail.com/3-0001.if_endif.v24.patch)
  download | inline diff:
From da8306acab0f0304d62f966c5217139bacfe722e Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sat, 18 Mar 2017 12:47:25 -0400
Subject: [PATCH] Add \if...\endif blocks

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1513 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql-on-error-stop.out   |  506 +++++++
 src/test/regress/expected/psql.out                 |  100 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  100 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 20 files changed, 2316 insertions(+), 284 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql-on-error-stop.out
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..a8b8ec2 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,33 +201,130 @@ 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;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	free(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +332,82 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+	const char	*warning_context = (active_branch) ? prefix : NULL;
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									warning_context,
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +451,23 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+	{
+		char	   *opt = psql_scan_slash_option(scan_state,
+												 OT_NO_EVAL, NULL, true);
+		free(opt);
+	}
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +507,71 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		char *p;
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+		{
+			p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			free(p);
+		}
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +630,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +693,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +716,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +736,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +750,42 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
+		}
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		char *pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+		{
+			char	   *pattern2 = psql_scan_slash_option(scan_state,
+											  OT_NO_EVAL, NULL, true);
+			free(pattern2);
 		}
 
-		if (pattern)
-			free(pattern);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +818,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +837,41 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+		{
+			char	   *fname;
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+			fname = psql_scan_slash_option(scan_state,
+										   OT_NO_EVAL, NULL, true);
+			if (fname)
+			{
+				char	   *ln;
+				/* try to get separate lineno arg */
+				ln = psql_scan_slash_option(scan_state,
+											OT_NO_EVAL, NULL, true);
+				if (ln)
+					free(ln);
+			}
+			if (fname)
+				free(fname);
+		}
+	}
+
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +882,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +900,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +916,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +953,36 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
+	return true;
+}
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +993,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1011,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1024,48 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1094,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1135,21 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+	{
+		encoding = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (encoding)
+			free(encoding);
+	}
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1169,45 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1219,38 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
-
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
+
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,16 +1260,28 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		prefix = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 	}
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
 		size_t		len;
 
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+
 		/* strip any trailing spaces and semicolons */
 		if (opt)
 		{
@@ -973,9 +1295,23 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1319,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,33 +1350,217 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	switch (conditional_stack_peek(cstack))
 	{
-		char	   *pattern;
-		bool		show_verbose;
+		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;
+	}
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+	return true;
+}
 
-		show_verbose = strchr(cmd, '+') ? true : false;
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
 
-		success = listAllDbs(pattern, show_verbose);
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
 
-		if (pattern)
-			free(pattern);
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
 	}
 
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
 	/*
-	 * large object things
+	 * get rid of this ifstate element and look at the previous
+	 * one, if any
 	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
+	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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *pattern;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt1,
-				   *opt2;
+		bool		show_verbose;
+
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
+		show_verbose = strchr(cmd, '+') ? true : false;
 
+		success = listAllDbs(pattern, show_verbose);
+
+	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+	}
+
+	if (pattern)
+		free(pattern);
+
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1609,57 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+	{
+		opt1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt1)
+		{
+			free(opt1);
+			opt2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			if (opt2)
+				free(opt2);
+		}
+	}
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1668,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1731,28 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+	{
+		char	*p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1806,33 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+	{
+		arg1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (arg1)
+		{
+			free(arg1);
+			arg2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (arg2)
+				free(arg2);
+		}
+	}
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1867,37 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+		if (opt0)
+		{
+			free(opt0);
+			opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+			if (opt1)
+				free(opt1);
+		}
+	}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+	return success;
+}
+
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1905,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1927,26 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1982,28 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt0)
+			free(opt0);
+	}
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +2040,33 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+	{
+		envvar = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		if (envvar)
+		{
+			free(envvar);
+			envval = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (envval)
+				free(envval);
+		}
+	}
+
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +2079,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2142,27 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+	{
+		func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (func)
+			free(func);
+	}
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2175,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2232,75 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+	{
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+		if (view)
+			free(view);
+	}
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+	{
+		value = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (value)
+			free(value);
+	}
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2315,26 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2347,35 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2433,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2475,98 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern)
+			free(pattern);
+	}
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1735,6 +2577,139 @@ exec_command(const char *cmd,
 		else
 			slashUsage(pset.popt.topt.pager);
 	}
+	else
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+
+	if (opt0)
+		free(opt0);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
+	}
+
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
 
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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-on-error-stop.out b/src/test/regress/expected/psql-on-error-stop.out
new file mode 100644
index 0000000..c812e97
--- /dev/null
+++ b/src/test/regress/expected/psql-on-error-stop.out
@@ -0,0 +1,506 @@
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+
+-- \set
+
+-- fail: invalid name
+\set invalid/name foo
+-- fail: invalid value for special variable
+\set AUTOCOMMIT foo
+\set FETCH_COUNT foo
+-- check handling of built-in boolean variable
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK foo
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+\echo :ON_ERROR_ROLLBACK
+\unset ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+
+-- \g and \gx
+
+SELECT 1 as one, 2 as two \g
+\gx
+SELECT 3 as three, 4 as four \gx
+\g
+
+-- \gset
+
+select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+
+\echo :pref01_test01 :pref01_test02 :pref01_test03
+
+-- should fail: bad variable name
+select 10 as "bad name"
+\gset
+
+-- multiple backslash commands in one line
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+-- NULL should unset the variable
+\set var2 xyz
+select 1 as var1, NULL as var2, 3 as var3 \gset
+\echo :var1 :var2 :var3
+
+-- \gset requires just one tuple
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+-- \gset should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+\unset FETCH_COUNT
+
+-- \gexec
+
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+
+\unset FETCH_COUNT
+
+-- show all pset options
+\pset
+
+-- test multi-line headers, wrapping, and newline indicators
+prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
+
+c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
+bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+-- test single-line header and data
+prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 30
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+\pset linestyle ascii
+
+prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
+
+\pset format asciidoc
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+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'
+			\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
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT errors
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT always
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
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/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ea7b5b4..ddc160d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index cf48ea7..02278b2 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
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
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 17:18  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 2 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-19 17:18 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v24 highlights:

The v24 patch is twice larger that the previous submission. Sigh.

If I'm reading headers correctly, it seems that it adds an 
"expected/psql-on-error-stop.out" file without a corresponding test source 
in "sql/". Is this file to be simply ignored, or is a source missing?

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 17:26  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 232+ messages in thread

From: Alvaro Herrera @ 2017-03-19 17:26 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Fabien COELHO wrote:
> 
> Hello Corey,
> 
> > v24 highlights:
> 
> The v24 patch is twice larger that the previous submission. Sigh.

The reason this is so large is that there is an entangled refactoring
patch, splitting the exec_command() function from one giant switch()
into one routine for each command.  It's up to the committer whether to
do it all in one patch, or to request this to be split into a
refactoring patch plus another adding functionality on top.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 17:48  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-19 17:48 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Alvaro Herrera <alvherre@2ndquadrant.com> writes:
> The reason this is so large is that there is an entangled refactoring
> patch, splitting the exec_command() function from one giant switch()
> into one routine for each command.  It's up to the committer whether to
> do it all in one patch, or to request this to be split into a
> refactoring patch plus another adding functionality on top.

Assuming we want to do it that way at all, two steps would probably be
easier to review in detail.

I'm not entirely convinced that function-per-command is an improvement
though.  Seems like it would only help to the extent that you could do a
simple "return" to implement early exit, and it looks to me like that
doesn't work in a lot of places because you still have to clean up things
like malloc'd argument strings before you can return.  So the question
we have to answer is whether this way looks cleaner than what we'd get if
we just changed the logic in-place.  For the purpose of answering that
question, looking at the final state is the right thing to do.

I don't have a definite opinion on that core question yet, since I've not
read this version of the patch.  Anybody else want to give an opinion?

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 20:00  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Alvaro Herrera @ 2017-03-19 20:00 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Tom Lane wrote:

> I'm not entirely convinced that function-per-command is an improvement
> though.  Seems like it would only help to the extent that you could do a
> simple "return" to implement early exit, and it looks to me like that
> doesn't work in a lot of places because you still have to clean up things
> like malloc'd argument strings before you can return.  So the question
> we have to answer is whether this way looks cleaner than what we'd get if
> we just changed the logic in-place.  For the purpose of answering that
> question, looking at the final state is the right thing to do.
> 
> I don't have a definite opinion on that core question yet, since I've not
> read this version of the patch.  Anybody else want to give an opinion?

Currently, exec_command is a 1500-line function.  If I had to see how a
single \-command worked, I would have to fold everything but the command
I'm interested in, in case there's something nontrivial at function
start or end (or even in between -- I would have to start by figuring
out whether there's anything other than "else if" somewhere in those
1500 lines).  I think splitting into command-specific functions makes
this much easier to follow, particularly if we want to add extra tricks
such as returning early etc.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 20:23  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-19 20:23 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

> I'm not entirely convinced that function-per-command is an improvement
> though. [...]

> I don't have a definite opinion on that core question yet, since I've not
> read this version of the patch.  Anybody else want to give an opinion?

My 0.02€:

I've already provided my view...

Personnally I like good functions. Maybe a per-command-family set of 
functions could improve the code readability, but (1) I'm not sure this is 
achieved by this patch (eg the if-related state management is now 
dispatched in 4 functions) and (2) I'm not sure that this approach helps 
much with respect to trying to factor out backslash-command-related 
active-or-not argument management.

However I have not looked at the patch in detail. I'm planing to do so 
later this week.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 21:05  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-19 21:05 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Alvaro Herrera <alvherre@2ndquadrant.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sun, Mar 19, 2017 at 4:23 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Tom,
>
> I'm not entirely convinced that function-per-command is an improvement
>> though. [...]
>>
>
> I don't have a definite opinion on that core question yet, since I've not
>> read this version of the patch.  Anybody else want to give an opinion?
>>
>
> My 0.02€:
>
> I've already provided my view...
>
> Personnally I like good functions. Maybe a per-command-family set of
> functions could improve the code readability, but (1) I'm not sure this is
> achieved by this patch (eg the if-related state management is now
> dispatched in 4 functions) and (2) I'm not sure that this approach helps
> much with respect to trying to factor out backslash-command-related
> active-or-not argument management.
>
> However I have not looked at the patch in detail. I'm planing to do so
> later this week.


I offered to split the patch into two steps (1. break each "family" into
it's own function and 2. Do what's needed for \if-\endif) but got no
response. I can still do that if people think it's worthwhile.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-19 21:18  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-19 21:18 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sun, Mar 19, 2017 at 1:18 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> v24 highlights:
>>
>
> The v24 patch is twice larger that the previous submission. Sigh.
>
> If I'm reading headers correctly, it seems that it adds an
> "expected/psql-on-error-stop.out" file without a corresponding test
> source in "sql/". Is this file to be simply ignored, or is a source missing?


Ignore it. I created the new .sql/.out pair, realized that the file naming
convention was underscores not dashes, changed them and evidently forgot
that I had already added a dashed one to git.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-24 16:52  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-24 16:52 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v24 highlights:
>
> - finally using git format-patch
> - all conditional slash commands broken out into their own functions
> (exec_command_$NAME) , each one tests if it's in an active branch, and if
> it's not it consumes the same number of parameters, but discards them.
> comments for each slash-command family were left as-is, may need to be
> bulked up.
> - TAP tests discarded in favor of one ON_EROR_STOP test for invalid \elif
> placement
> - documentation recommending ON_ERROR_STOP removed, because invalid
> expressions no longer throw off if-endif balance
> - documentation updated to reflex that contextually-correct-but-invalid
> boolean expressions are treated as false
> - psql_get_variable has a passthrough void pointer now, but I ended up not
> needing it. Instead, all slash commands in false blocks either fetch
> OT_NO_EVAL or OT_WHOLE_LINE options. If I'm missing something, let me know.

A few comments about the patch.

Patch applies. "make check" ok.

As already pointed out, there is one useless file in the patch.

Although currently there is only one expected argument for boolean 
expressions, the n² concatenation algorithm in gather_boolean_expression 
is not very elegant. Is there some string buffer data structure which 
could be used instead?

ISTM that ignore_boolean_expression may call free on a NULL pointer if the 
expression is empty?

Generally I find the per-command functions rather an improvement.

However there is an impact on testing because of all these changes. ISTM 
that test cases should reflect this situation and test that \cd, \edit, 
... are indeed ignored properly and taking account there expected args...

In "exec_command_connect" an argument is changed from "-reuse-previous" to 
"-reuse-previous=", not sure why.

There seems to be pattern repetition for _ev _ef and _sf _sv. Would it 
make sense to create a function instead of keeping the initial copy-paste?

I think that some functions could be used for some repeated cases such as 
"discard one arg", "discard one or two arg", "discard whole line", for the 
various inactive branches, so as to factor out code.

I would suggest to put together all if-related backslash command, 
so that the stack management is all in one function instead of 4.

For pset the inactive branch does OT_NORMAL instead of OT_NOT_EVAL, not 
sure why.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-24 19:16  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-24 19:16 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> A few comments about the patch.
>
> Patch applies. "make check" ok.
>
> As already pointed out, there is one useless file in the patch.
>
> Although currently there is only one expected argument for boolean
> expressions, the n² concatenation algorithm in gather_boolean_expression is
> not very elegant. Is there some string buffer data structure which could be
> used instead?
>

I wished for the same thing, happy to use one if it is made known to me.
I pulled that pattern from somewhere else in the code, and given that the
max number of args for a command is around 4, I'm not too worried about
scaling.


>
> ISTM that ignore_boolean_expression may call free on a NULL pointer if the
> expression is empty?
>

True. The psql code is actually littered with a lot of un-checked free(p)
calls, so I started to wonder if maybe we had a wrapper on free() that
checked for NULL. I'll fix this one just to be consistent.


>
> Generally I find the per-command functions rather an improvement.
>

I did too. I tried to split this patch up into two parts, one that broke
out the functions, and one that added if-then, and found that the first
patch was just as unwieldily without the if-then stuff as with.


>
> However there is an impact on testing because of all these changes. ISTM
> that test cases should reflect this situation and test that \cd, \edit, ...
> are indeed ignored properly and taking account there expected args...
>

I think one grand

\if false
\a
\c some_connect_string
...
\z some_table_name
\endif

should do the trick, but it wouldn't detect memory leaks.


>
> In "exec_command_connect" an argument is changed from "-reuse-previous" to
> "-reuse-previous=", not sure why.
>

It shouldn't have been. Good catch. Most commands were able to be migrated
with simple changes (status => *status, strcmp() if-block becomes
active-if-block, etc), but that one was slightly different.


>
> There seems to be pattern repetition for _ev _ef and _sf _sv. Would it
> make sense to create a function instead of keeping the initial copy-paste?
>

Yes, and a few things like that, but I wanted this patch to keep as much
code as-is as possible.


>
> I think that some functions could be used for some repeated cases such as
> "discard one arg", "discard one or two arg", "discard whole line", for the
> various inactive branches, so as to factor out code.
>

I'd be in favor of that as well


>
> I would suggest to put together all if-related backslash command, so that
> the stack management is all in one function instead of 4.
>

I recognize the urge to group them together, but would there be any actual
code sharing between them? Wouldn't I be either re-checking the string
"cmd" again, or otherwise setting an enum that I immediately re-check
inside the all_branching_commands() function?


>
> For pset the inactive branch does OT_NORMAL instead of OT_NOT_EVAL, not
> sure why.


An oversight. Good catch.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-24 20:10  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-24 20:10 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> I wished for the same thing, happy to use one if it is made known to me.
> I pulled that pattern from somewhere else in the code, and given that the
> max number of args for a command is around 4, I'm not too worried about
> scaling.

If there are expressions one day like pgbench, the number of arguments 
becomes arbitrary. Have you looked at PQExpBuffer?

>> However there is an impact on testing because of all these changes. ISTM
>> that test cases should reflect this situation and test that \cd, \edit, ...
>> are indeed ignored properly and taking account there expected args...
>
> I think one grand
>
> \if false
> \a
> \c some_connect_string
> ...
> \z some_table_name
> \endif
> should do the trick,

Yes. Maybe some commands could be on the same line as well.

> but it wouldn't detect memory leaks.

No miracle...

>> There seems to be pattern repetition for _ev _ef and _sf _sv. Would it
>> make sense to create a function instead of keeping the initial copy-paste?
>
> Yes, and a few things like that, but I wanted this patch to keep as much
> code as-is as possible.

If you put the generic function at the same place, one would be more or 
less kept and the other would be just removed?

"git diff --patience -w" gives a rather convenient output for looking at 
the patch.

>> I would suggest to put together all if-related backslash command, so that
>> the stack management is all in one function instead of 4.
>
> I recognize the urge to group them together, but would there be any actual
> code sharing between them? Wouldn't I be either re-checking the string
> "cmd" again, or otherwise setting an enum that I immediately re-check
> inside the all_branching_commands() function?

I do not see that as a significant issue, especially compared to the 
benefit of having the automaton transition management in a single place.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-24 20:33  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-24 20:33 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Fri, Mar 24, 2017 at 4:10 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> I wished for the same thing, happy to use one if it is made known to me.
>> I pulled that pattern from somewhere else in the code, and given that the
>> max number of args for a command is around 4, I'm not too worried about
>> scaling.
>>
>
> If there are expressions one day like pgbench, the number of arguments
> becomes arbitrary. Have you looked at PQExpBuffer?


I will look into it.


>
>>> There seems to be pattern repetition for _ev _ef and _sf _sv. Would it
>>> make sense to create a function instead of keeping the initial
>>> copy-paste?
>>>
>>
>> Yes, and a few things like that, but I wanted this patch to keep as much
>> code as-is as possible.
>>
>
> If you put the generic function at the same place, one would be more or
> less kept and the other would be just removed?


> "git diff --patience -w" gives a rather convenient output for looking at
> the patch.


Good to know about that option.

As for a function for digested ignored slash options, it seems like I can
disregard the true/false value of the "semicolon" parameter. Is that
correct?


> I would suggest to put together all if-related backslash command, so that
>>> the stack management is all in one function instead of 4.
>>>
>>
>> I recognize the urge to group them together, but would there be any actual
>> code sharing between them? Wouldn't I be either re-checking the string
>> "cmd" again, or otherwise setting an enum that I immediately re-check
>> inside the all_branching_commands() function?
>>
>
> I do not see that as a significant issue, especially compared to the
> benefit of having the automaton transition management in a single place.


I'm still struggling to see how this would add any clarity to the code
beyond what I can achieve by clustering the
exec_command_(if/elif/else/endif) near one another.

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-24 22:53  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-24 22:53 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

v25

- PQExpBuffer on gather_boolean_expression()
- convenience/clarity functions: ignore_slash_option(),
ignore_2_slash_options(), ignore_slash_line()
- remove inaccurate test of variable expansion in a false block
- added kitchen-sink test of parsing slash commands in a false block
- removed spurious file that shouldn't have been in v24
- removed any potential free(NULL) calls *that I introduced*, others remain
from master branch

NOT done:
- grouping all branching commands into one function - can be done in a
later patch for clarity
- combining _ef / _ev or _sf / _sv - can be done in a later patch for
clarity


On Fri, Mar 24, 2017 at 4:33 PM, Corey Huinker <corey.huinker@gmail.com>
wrote:

> On Fri, Mar 24, 2017 at 4:10 PM, Fabien COELHO <coelho@cri.ensmp.fr>
> wrote:
>
>>
>> Hello Corey,
>>
>> I wished for the same thing, happy to use one if it is made known to me.
>>> I pulled that pattern from somewhere else in the code, and given that the
>>> max number of args for a command is around 4, I'm not too worried about
>>> scaling.
>>>
>>
>> If there are expressions one day like pgbench, the number of arguments
>> becomes arbitrary. Have you looked at PQExpBuffer?
>
>
> I will look into it.
>
>
>>
>>>> There seems to be pattern repetition for _ev _ef and _sf _sv. Would it
>>>> make sense to create a function instead of keeping the initial
>>>> copy-paste?
>>>>
>>>
>>> Yes, and a few things like that, but I wanted this patch to keep as much
>>> code as-is as possible.
>>>
>>
>> If you put the generic function at the same place, one would be more or
>> less kept and the other would be just removed?
>
>
>> "git diff --patience -w" gives a rather convenient output for looking at
>> the patch.
>
>
> Good to know about that option.
>
> As for a function for digested ignored slash options, it seems like I can
> disregard the true/false value of the "semicolon" parameter. Is that
> correct?
>
>
>> I would suggest to put together all if-related backslash command, so that
>>>> the stack management is all in one function instead of 4.
>>>>
>>>
>>> I recognize the urge to group them together, but would there be any
>>> actual
>>> code sharing between them? Wouldn't I be either re-checking the string
>>> "cmd" again, or otherwise setting an enum that I immediately re-check
>>> inside the all_branching_commands() function?
>>>
>>
>> I do not see that as a significant issue, especially compared to the
>> benefit of having the automaton transition management in a single place.
>
>
> I'm still struggling to see how this would add any clarity to the code
> beyond what I can achieve by clustering the exec_command_(if/elif/else/endif)
> near one another.
>
>
>
>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] 0001.if_endif.v25.patch (98.3K, ../../CADkLM=e8pN4euMZp43N8S9obJoee1xsn_vkbusHwv01R5pb93g@mail.gmail.com/3-0001.if_endif.v25.patch)
  download | inline diff:
From da8306acab0f0304d62f966c5217139bacfe722e Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sat, 18 Mar 2017 12:47:25 -0400
Subject: [PATCH 1/2] Add \if...\endif blocks

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1513 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql-on-error-stop.out   |  506 +++++++
 src/test/regress/expected/psql.out                 |  100 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  100 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 20 files changed, 2316 insertions(+), 284 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql-on-error-stop.out
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..a8b8ec2 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,33 +201,130 @@ 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;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	free(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +332,82 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+	const char	*warning_context = (active_branch) ? prefix : NULL;
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									warning_context,
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +451,23 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+	{
+		char	   *opt = psql_scan_slash_option(scan_state,
+												 OT_NO_EVAL, NULL, true);
+		free(opt);
+	}
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +507,71 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		char *p;
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+		{
+			p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			free(p);
+		}
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +630,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +693,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +716,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +736,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +750,42 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
+		}
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		char *pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+		{
+			char	   *pattern2 = psql_scan_slash_option(scan_state,
+											  OT_NO_EVAL, NULL, true);
+			free(pattern2);
 		}
 
-		if (pattern)
-			free(pattern);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +818,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +837,41 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+		{
+			char	   *fname;
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+			fname = psql_scan_slash_option(scan_state,
+										   OT_NO_EVAL, NULL, true);
+			if (fname)
+			{
+				char	   *ln;
+				/* try to get separate lineno arg */
+				ln = psql_scan_slash_option(scan_state,
+											OT_NO_EVAL, NULL, true);
+				if (ln)
+					free(ln);
+			}
+			if (fname)
+				free(fname);
+		}
+	}
+
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +882,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +900,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +916,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +953,36 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
+	return true;
+}
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +993,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1011,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1024,48 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1094,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1135,21 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+	{
+		encoding = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (encoding)
+			free(encoding);
+	}
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1169,45 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1219,38 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
-
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
+
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,16 +1260,28 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		prefix = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 	}
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
 		size_t		len;
 
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+
 		/* strip any trailing spaces and semicolons */
 		if (opt)
 		{
@@ -973,9 +1295,23 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1319,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,33 +1350,217 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	switch (conditional_stack_peek(cstack))
 	{
-		char	   *pattern;
-		bool		show_verbose;
+		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;
+	}
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+	return true;
+}
 
-		show_verbose = strchr(cmd, '+') ? true : false;
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
 
-		success = listAllDbs(pattern, show_verbose);
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
 
-		if (pattern)
-			free(pattern);
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
 	}
 
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
 	/*
-	 * large object things
+	 * get rid of this ifstate element and look at the previous
+	 * one, if any
 	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
+	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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *pattern;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt1,
-				   *opt2;
+		bool		show_verbose;
+
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
+		show_verbose = strchr(cmd, '+') ? true : false;
 
+		success = listAllDbs(pattern, show_verbose);
+
+	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+	}
+
+	if (pattern)
+		free(pattern);
+
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1609,57 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+	{
+		opt1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt1)
+		{
+			free(opt1);
+			opt2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			if (opt2)
+				free(opt2);
+		}
+	}
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1668,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1731,28 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+	{
+		char	*p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1806,33 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+	{
+		arg1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (arg1)
+		{
+			free(arg1);
+			arg2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (arg2)
+				free(arg2);
+		}
+	}
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1867,37 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+		if (opt0)
+		{
+			free(opt0);
+			opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+			if (opt1)
+				free(opt1);
+		}
+	}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+	return success;
+}
+
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1905,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1927,26 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1982,28 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt0)
+			free(opt0);
+	}
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +2040,33 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+	{
+		envvar = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		if (envvar)
+		{
+			free(envvar);
+			envval = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (envval)
+				free(envval);
+		}
+	}
+
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +2079,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2142,27 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+	{
+		func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (func)
+			free(func);
+	}
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2175,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2232,75 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+	{
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+		if (view)
+			free(view);
+	}
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+	{
+		value = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (value)
+			free(value);
+	}
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2315,26 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2347,35 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2433,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2475,98 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern)
+			free(pattern);
+	}
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1735,6 +2577,139 @@ exec_command(const char *cmd,
 		else
 			slashUsage(pset.popt.topt.pager);
 	}
+	else
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+
+	if (opt0)
+		free(opt0);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
+	}
+
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
 
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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-on-error-stop.out b/src/test/regress/expected/psql-on-error-stop.out
new file mode 100644
index 0000000..c812e97
--- /dev/null
+++ b/src/test/regress/expected/psql-on-error-stop.out
@@ -0,0 +1,506 @@
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+
+-- \set
+
+-- fail: invalid name
+\set invalid/name foo
+-- fail: invalid value for special variable
+\set AUTOCOMMIT foo
+\set FETCH_COUNT foo
+-- check handling of built-in boolean variable
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK foo
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+\echo :ON_ERROR_ROLLBACK
+\unset ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+
+-- \g and \gx
+
+SELECT 1 as one, 2 as two \g
+\gx
+SELECT 3 as three, 4 as four \gx
+\g
+
+-- \gset
+
+select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+
+\echo :pref01_test01 :pref01_test02 :pref01_test03
+
+-- should fail: bad variable name
+select 10 as "bad name"
+\gset
+
+-- multiple backslash commands in one line
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+-- NULL should unset the variable
+\set var2 xyz
+select 1 as var1, NULL as var2, 3 as var3 \gset
+\echo :var1 :var2 :var3
+
+-- \gset requires just one tuple
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+-- \gset should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+\unset FETCH_COUNT
+
+-- \gexec
+
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+
+\unset FETCH_COUNT
+
+-- show all pset options
+\pset
+
+-- test multi-line headers, wrapping, and newline indicators
+prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
+
+c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
+bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+-- test single-line header and data
+prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 30
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+\pset linestyle ascii
+
+prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
+
+\pset format asciidoc
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+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'
+			\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
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT errors
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT always
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
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/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ea7b5b4..ddc160d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index cf48ea7..02278b2 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
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
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4


From 6af2465eb09888b1548b9ba22a2908cdbc04aac3 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sun, 19 Mar 2017 17:44:07 -0400
Subject: [PATCH 2/2] remove

---
 src/test/regress/expected/psql-on-error-stop.out | 506 -----------------------
 1 file changed, 506 deletions(-)
 delete mode 100644 src/test/regress/expected/psql-on-error-stop.out

diff --git a/src/test/regress/expected/psql-on-error-stop.out b/src/test/regress/expected/psql-on-error-stop.out
deleted file mode 100644
index c812e97..0000000
--- a/src/test/regress/expected/psql-on-error-stop.out
+++ /dev/null
@@ -1,506 +0,0 @@
---
--- Tests for psql features that aren't closely connected to any
--- specific server features
---
-
--- \set
-
--- fail: invalid name
-\set invalid/name foo
--- fail: invalid value for special variable
-\set AUTOCOMMIT foo
-\set FETCH_COUNT foo
--- check handling of built-in boolean variable
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK foo
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK on
-\echo :ON_ERROR_ROLLBACK
-\unset ON_ERROR_ROLLBACK
-\echo :ON_ERROR_ROLLBACK
-
--- \g and \gx
-
-SELECT 1 as one, 2 as two \g
-\gx
-SELECT 3 as three, 4 as four \gx
-\g
-
--- \gset
-
-select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
-
-\echo :pref01_test01 :pref01_test02 :pref01_test03
-
--- should fail: bad variable name
-select 10 as "bad name"
-\gset
-
--- multiple backslash commands in one line
-select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
-select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
-select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
-select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
-
--- NULL should unset the variable
-\set var2 xyz
-select 1 as var1, NULL as var2, 3 as var3 \gset
-\echo :var1 :var2 :var3
-
--- \gset requires just one tuple
-select 10 as test01, 20 as test02 from generate_series(1,3) \gset
-select 10 as test01, 20 as test02 from generate_series(1,0) \gset
-
--- \gset should work in FETCH_COUNT mode too
-\set FETCH_COUNT 1
-
-select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
-select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
-select 10 as test01, 20 as test02 from generate_series(1,3) \gset
-select 10 as test01, 20 as test02 from generate_series(1,0) \gset
-
-\unset FETCH_COUNT
-
--- \gexec
-
-create temporary table gexec_test(a int, b text, c date, d float);
-select format('create index on gexec_test(%I)', attname)
-from pg_attribute
-where attrelid = 'gexec_test'::regclass and attnum > 0
-order by attnum
-\gexec
-
--- \gexec should work in FETCH_COUNT mode too
--- (though the fetch limit applies to the executed queries not the meta query)
-\set FETCH_COUNT 1
-
-select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
-union all
-select 'drop table gexec_test', NULL
-union all
-select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-\gexec
-
-\unset FETCH_COUNT
-
--- show all pset options
-\pset
-
--- test multi-line headers, wrapping, and newline indicators
-prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-
-c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
-bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
-
-\pset linestyle ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset linestyle old-ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-deallocate q;
-
--- test single-line header and data
-prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
-
-\pset linestyle ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 30
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset linestyle old-ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-deallocate q;
-
-\pset linestyle ascii
-
-prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
-
-\pset format asciidoc
-\pset expanded off
-\pset border 0
-execute q;
-
-\pset border 1
-execute q;
-
-\pset border 2
-execute q;
-
-\pset expanded on
-\pset border 0
-execute q;
-
-\pset border 1
-execute q;
-
-\pset border 2
-execute q;
-
-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'
-			\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
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-
-\set SHOW_CONTEXT errors
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-
-\set SHOW_CONTEXT always
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-25 06:17  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-25 06:17 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v25

ISTM that the attached file contents is identical to v24.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-25 06:21  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-25 06:21 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> As for a function for digested ignored slash options, it seems like I can
> disregard the true/false value of the "semicolon" parameter. Is that
> correct?

Dunno.

>> I do not see that as a significant issue, especially compared to the
>> benefit of having the automaton transition management in a single place.
>
> I'm still struggling to see how this would add any clarity to the code
> beyond what I can achieve by clustering the
> exec_command_(if/elif/else/endif) near one another.

Hmmm... it is more cleanly encapsulated if in just one function?

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-25 15:33  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-25 15:33 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Sat, Mar 25, 2017 at 2:17 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> v25
>>
>
> ISTM that the attached file contents is identical to v24.
>
> --
> Fabien.
>


v25, try 2:

First file is what you were used to last time. 2nd and 3rd are changes
since then based on feedback.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001-Add-if.-endif-blocks.patch (88.6K, ../../CADkLM=esbih=DF97=87UjpT-8JjYnLCBy_NFMyM0vs-0jzYe3A@mail.gmail.com/3-0001-Add-if.-endif-blocks.patch)
  download | inline diff:
From da8306acab0f0304d62f966c5217139bacfe722e Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sat, 18 Mar 2017 12:47:25 -0400
Subject: [PATCH 1/3] Add \if...\endif blocks

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1513 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql-on-error-stop.out   |  506 +++++++
 src/test/regress/expected/psql.out                 |  100 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  100 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 20 files changed, 2316 insertions(+), 284 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql-on-error-stop.out
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..a8b8ec2 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,33 +201,130 @@ 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;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	free(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +332,82 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+	const char	*warning_context = (active_branch) ? prefix : NULL;
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									warning_context,
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +451,23 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+	{
+		char	   *opt = psql_scan_slash_option(scan_state,
+												 OT_NO_EVAL, NULL, true);
+		free(opt);
+	}
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +507,71 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		char *p;
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+		{
+			p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			free(p);
+		}
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +630,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +693,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +716,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +736,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +750,42 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
+		}
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		char *pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+		{
+			char	   *pattern2 = psql_scan_slash_option(scan_state,
+											  OT_NO_EVAL, NULL, true);
+			free(pattern2);
 		}
 
-		if (pattern)
-			free(pattern);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +818,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +837,41 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+		{
+			char	   *fname;
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+			fname = psql_scan_slash_option(scan_state,
+										   OT_NO_EVAL, NULL, true);
+			if (fname)
+			{
+				char	   *ln;
+				/* try to get separate lineno arg */
+				ln = psql_scan_slash_option(scan_state,
+											OT_NO_EVAL, NULL, true);
+				if (ln)
+					free(ln);
+			}
+			if (fname)
+				free(fname);
+		}
+	}
+
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +882,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +900,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +916,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +953,36 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
+	return true;
+}
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +993,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1011,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1024,48 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+	{
+		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1094,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1135,21 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+	{
+		encoding = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (encoding)
+			free(encoding);
+	}
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1169,45 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1219,38 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
-
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (fname)
+			free(fname);
+	}
+
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,16 +1260,28 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		prefix = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 	}
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
 		size_t		len;
 
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+
 		/* strip any trailing spaces and semicolons */
 		if (opt)
 		{
@@ -973,9 +1295,23 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1319,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,33 +1350,217 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	switch (conditional_stack_peek(cstack))
 	{
-		char	   *pattern;
-		bool		show_verbose;
+		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;
+	}
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+	return true;
+}
 
-		show_verbose = strchr(cmd, '+') ? true : false;
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
 
-		success = listAllDbs(pattern, show_verbose);
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
 
-		if (pattern)
-			free(pattern);
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
 	}
 
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
 	/*
-	 * large object things
+	 * get rid of this ifstate element and look at the previous
+	 * one, if any
 	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
+	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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *pattern;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt1,
-				   *opt2;
+		bool		show_verbose;
+
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
+		show_verbose = strchr(cmd, '+') ? true : false;
 
+		success = listAllDbs(pattern, show_verbose);
+
+	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+	}
+
+	if (pattern)
+		free(pattern);
+
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1609,57 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+	{
+		opt1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt1)
+		{
+			free(opt1);
+			opt2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+			if (opt2)
+				free(opt2);
+		}
+	}
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1668,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1731,28 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+	{
+		char	*p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (p)
+			free(p);
+	}
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1806,33 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+	{
+		arg1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (arg1)
+		{
+			free(arg1);
+			arg2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (arg2)
+				free(arg2);
+		}
+	}
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1867,37 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+		if (opt0)
+		{
+			free(opt0);
+			opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+			if (opt1)
+				free(opt1);
+		}
+	}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+	return success;
+}
+
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1905,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1927,26 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1982,28 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+	{
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt0)
+			free(opt0);
+	}
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +2040,33 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+	{
+		envvar = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		if (envvar)
+		{
+			free(envvar);
+			envval = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+			if (envval)
+				free(envval);
+		}
+	}
+
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +2079,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2142,27 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+	{
+		func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+		if (func)
+			free(func);
+	}
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2175,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2232,75 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+	{
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+		if (view)
+			free(view);
+	}
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+	{
+		value = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (value)
+			free(value);
+	}
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2315,26 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2347,35 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2433,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2475,98 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (opt)
+			free(opt);
+	}
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+	{
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern)
+			free(pattern);
+	}
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+	{
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		if (opt)
+			free(opt);
+	}
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1735,6 +2577,139 @@ exec_command(const char *cmd,
 		else
 			slashUsage(pset.popt.topt.pager);
 	}
+	else
+		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+
+	if (opt0)
+		free(opt0);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
+	}
+
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
 
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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-on-error-stop.out b/src/test/regress/expected/psql-on-error-stop.out
new file mode 100644
index 0000000..c812e97
--- /dev/null
+++ b/src/test/regress/expected/psql-on-error-stop.out
@@ -0,0 +1,506 @@
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+
+-- \set
+
+-- fail: invalid name
+\set invalid/name foo
+-- fail: invalid value for special variable
+\set AUTOCOMMIT foo
+\set FETCH_COUNT foo
+-- check handling of built-in boolean variable
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK foo
+\echo :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+\echo :ON_ERROR_ROLLBACK
+\unset ON_ERROR_ROLLBACK
+\echo :ON_ERROR_ROLLBACK
+
+-- \g and \gx
+
+SELECT 1 as one, 2 as two \g
+\gx
+SELECT 3 as three, 4 as four \gx
+\g
+
+-- \gset
+
+select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+
+\echo :pref01_test01 :pref01_test02 :pref01_test03
+
+-- should fail: bad variable name
+select 10 as "bad name"
+\gset
+
+-- multiple backslash commands in one line
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+-- NULL should unset the variable
+\set var2 xyz
+select 1 as var1, NULL as var2, 3 as var3 \gset
+\echo :var1 :var2 :var3
+
+-- \gset requires just one tuple
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+-- \gset should work in FETCH_COUNT mode too
+\set FETCH_COUNT 1
+
+select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+select 10 as test01, 20 as test02 from generate_series(1,3) \gset
+select 10 as test01, 20 as test02 from generate_series(1,0) \gset
+
+\unset FETCH_COUNT
+
+-- \gexec
+
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+
+\unset FETCH_COUNT
+
+-- show all pset options
+\pset
+
+-- test multi-line headers, wrapping, and newline indicators
+prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
+
+c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
+bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+-- test single-line header and data
+prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
+
+\pset linestyle ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 30
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+\pset columns 20
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset linestyle old-ascii
+
+\pset expanded off
+\pset columns 40
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset expanded on
+
+\pset border 0
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 1
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+\pset border 2
+\pset format unaligned
+execute q;
+\pset format aligned
+execute q;
+\pset format wrapped
+execute q;
+
+deallocate q;
+
+\pset linestyle ascii
+
+prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
+
+\pset format asciidoc
+\pset expanded off
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+\pset expanded on
+\pset border 0
+execute q;
+
+\pset border 1
+execute q;
+
+\pset border 2
+execute q;
+
+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'
+			\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
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT errors
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
+
+\set SHOW_CONTEXT always
+do $$
+begin
+  raise notice 'foo';
+  raise exception 'bar';
+end $$;
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/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ea7b5b4..ddc160d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index cf48ea7..02278b2 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
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
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



  [text/x-patch] 0002-remove.patch (9.7K, ../../CADkLM=esbih=DF97=87UjpT-8JjYnLCBy_NFMyM0vs-0jzYe3A@mail.gmail.com/4-0002-remove.patch)
  download | inline diff:
From 6af2465eb09888b1548b9ba22a2908cdbc04aac3 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sun, 19 Mar 2017 17:44:07 -0400
Subject: [PATCH 2/3] remove

---
 src/test/regress/expected/psql-on-error-stop.out | 506 -----------------------
 1 file changed, 506 deletions(-)
 delete mode 100644 src/test/regress/expected/psql-on-error-stop.out

diff --git a/src/test/regress/expected/psql-on-error-stop.out b/src/test/regress/expected/psql-on-error-stop.out
deleted file mode 100644
index c812e97..0000000
--- a/src/test/regress/expected/psql-on-error-stop.out
+++ /dev/null
@@ -1,506 +0,0 @@
---
--- Tests for psql features that aren't closely connected to any
--- specific server features
---
-
--- \set
-
--- fail: invalid name
-\set invalid/name foo
--- fail: invalid value for special variable
-\set AUTOCOMMIT foo
-\set FETCH_COUNT foo
--- check handling of built-in boolean variable
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK foo
-\echo :ON_ERROR_ROLLBACK
-\set ON_ERROR_ROLLBACK on
-\echo :ON_ERROR_ROLLBACK
-\unset ON_ERROR_ROLLBACK
-\echo :ON_ERROR_ROLLBACK
-
--- \g and \gx
-
-SELECT 1 as one, 2 as two \g
-\gx
-SELECT 3 as three, 4 as four \gx
-\g
-
--- \gset
-
-select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
-
-\echo :pref01_test01 :pref01_test02 :pref01_test03
-
--- should fail: bad variable name
-select 10 as "bad name"
-\gset
-
--- multiple backslash commands in one line
-select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
-select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
-select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
-select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
-
--- NULL should unset the variable
-\set var2 xyz
-select 1 as var1, NULL as var2, 3 as var3 \gset
-\echo :var1 :var2 :var3
-
--- \gset requires just one tuple
-select 10 as test01, 20 as test02 from generate_series(1,3) \gset
-select 10 as test01, 20 as test02 from generate_series(1,0) \gset
-
--- \gset should work in FETCH_COUNT mode too
-\set FETCH_COUNT 1
-
-select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
-select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
-select 10 as test01, 20 as test02 from generate_series(1,3) \gset
-select 10 as test01, 20 as test02 from generate_series(1,0) \gset
-
-\unset FETCH_COUNT
-
--- \gexec
-
-create temporary table gexec_test(a int, b text, c date, d float);
-select format('create index on gexec_test(%I)', attname)
-from pg_attribute
-where attrelid = 'gexec_test'::regclass and attnum > 0
-order by attnum
-\gexec
-
--- \gexec should work in FETCH_COUNT mode too
--- (though the fetch limit applies to the executed queries not the meta query)
-\set FETCH_COUNT 1
-
-select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
-union all
-select 'drop table gexec_test', NULL
-union all
-select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-\gexec
-
-\unset FETCH_COUNT
-
--- show all pset options
-\pset
-
--- test multi-line headers, wrapping, and newline indicators
-prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-
-c", array_to_string(array_agg(repeat('y',20-2*n)),E'\n') as "a
-bc" from generate_series(1,10) as n(n) group by n>1 order by n>1;
-
-\pset linestyle ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset linestyle old-ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-deallocate q;
-
--- test single-line header and data
-prepare q as select repeat('x',2*n) as "0123456789abcdef", repeat('y',20-2*n) as "0123456789" from generate_series(1,10) as n;
-
-\pset linestyle ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 30
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-\pset columns 20
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset linestyle old-ascii
-
-\pset expanded off
-\pset columns 40
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset expanded on
-
-\pset border 0
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 1
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-\pset border 2
-\pset format unaligned
-execute q;
-\pset format aligned
-execute q;
-\pset format wrapped
-execute q;
-
-deallocate q;
-
-\pset linestyle ascii
-
-prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
-
-\pset format asciidoc
-\pset expanded off
-\pset border 0
-execute q;
-
-\pset border 1
-execute q;
-
-\pset border 2
-execute q;
-
-\pset expanded on
-\pset border 0
-execute q;
-
-\pset border 1
-execute q;
-
-\pset border 2
-execute q;
-
-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'
-			\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
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-
-\set SHOW_CONTEXT errors
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-
-\set SHOW_CONTEXT always
-do $$
-begin
-  raise notice 'foo';
-  raise exception 'bar';
-end $$;
-- 
2.7.4



  [text/x-patch] 0003-Encapsulate-ignore-slash-options-operations.-Other-b.patch (16.9K, ../../CADkLM=esbih=DF97=87UjpT-8JjYnLCBy_NFMyM0vs-0jzYe3A@mail.gmail.com/5-0003-Encapsulate-ignore-slash-options-operations.-Other-b.patch)
  download | inline diff:
From 8c3852aa5572fe3e07af406c4c5cc248d66ee9c4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sat, 25 Mar 2017 11:26:41 -0400
Subject: [PATCH 3/3] Encapsulate ignore slash options operations. Other bug
 fixes.

---
 src/bin/psql/command.c             | 307 +++++++++++--------------------------
 src/test/regress/expected/psql.out |  28 ++--
 src/test/regress/sql/psql.sql      |  27 ++--
 3 files changed, 127 insertions(+), 235 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a8b8ec2..6ffddd5 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -210,24 +210,19 @@ 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;
+	PQExpBuffer exp_buf = createPQExpBuffer();
 	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);
-		}
+		/* add a space in between subsequent tokens */
+		if (num_options == 0)
+			appendPQExpBuffer(exp_buf, "%s", value);
 		else
-			expression_buffer = value;
+			appendPQExpBuffer(exp_buf, " %s", value);
+		num_options++;
 	}
 
 	/* currently, we expect exactly one option */
@@ -237,10 +232,10 @@ gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
 			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);
+						num_options, exp_buf->data);
 	}
 
-	return expression_buffer;
+	return exp_buf->data;
 }
 
 /*
@@ -249,7 +244,9 @@ gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
 static void
 ignore_boolean_expression(PsqlScanState scan_state)
 {
-	free(gather_boolean_expression(scan_state, false, false));
+	char	*p = gather_boolean_expression(scan_state, false, false);
+	if (p)
+		free(p);
 }
 
 /*
@@ -263,7 +260,8 @@ read_boolean_expression(PsqlScanState scan_state, char *action,
 {
 	char	*expr = gather_boolean_expression(scan_state, true, true);
 	bool	success = ParseVariableBool(expr, action, result);
-	free(expr);
+	if (expr)
+		free(expr);
 	return success;
 }
 
@@ -299,6 +297,39 @@ is_active_branch(PsqlScanState scan_state)
 	return conditional_active(get_conditional_stack(scan_state));
 }
 
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
 
 /*
  * \a -- toggle field alignment This makes little sense but we keep it
@@ -333,11 +364,7 @@ exec_command_C(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (p)
-			free(p);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -366,7 +393,6 @@ exec_command_connect(PsqlScanState scan_state)
 	enum trivalue reuse_previous = TRI_DEFAULT;
 	bool	success = true;
 	bool	active_branch = is_active_branch(scan_state);
-	const char	*warning_context = (active_branch) ? prefix : NULL;
 
 	opt1 = read_connect_arg(scan_state);
 	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
@@ -374,7 +400,8 @@ exec_command_connect(PsqlScanState scan_state)
 		bool		on_off;
 
 		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-									warning_context,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
 									&on_off);
 		if (success)
 		{
@@ -452,11 +479,7 @@ exec_command_cd(PsqlScanState scan_state, const char *cmd)
 			free(opt);
 	}
 	else
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NO_EVAL, NULL, true);
-		free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -552,12 +575,8 @@ exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
 	}
 	else
 	{
-		char *p;
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
-		{
-			p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-			free(p);
-		}
+			ignore_slash_option(scan_state);
 	}
 	return true;
 }
@@ -756,14 +775,9 @@ exec_command_d(PsqlScanState scan_state, const char *cmd,
 	else
 	{
 		/* digest and discard options as appropriate */
-		char *pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
 		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
-		{
-			char	   *pattern2 = psql_scan_slash_option(scan_state,
-											  OT_NO_EVAL, NULL, true);
-			free(pattern2);
-		}
-
+			ignore_slash_option(scan_state);
 	}
 
 	if (pattern)
@@ -840,23 +854,7 @@ exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
 	else
 	{
 		if (query_buf)
-		{
-			char	   *fname;
-
-			fname = psql_scan_slash_option(scan_state,
-										   OT_NO_EVAL, NULL, true);
-			if (fname)
-			{
-				char	   *ln;
-				/* try to get separate lineno arg */
-				ln = psql_scan_slash_option(scan_state,
-											OT_NO_EVAL, NULL, true);
-				if (ln)
-					free(ln);
-			}
-			if (fname)
-				free(fname);
-		}
+			ignore_2_slash_options(scan_state);
 	}
 
 	return true;
@@ -966,11 +964,8 @@ exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
 		}
 	}
 	else
-	{
-		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
-		if (p)
-			free(p);
-	}
+		ignore_slash_line(scan_state);
+
 	return true;
 }
 
@@ -1049,11 +1044,7 @@ exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
 		}
 	}
 	else
-	{
-		char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
-		if (p)
-			free(p);
-	}
+		ignore_slash_line(scan_state);
 
 	return true;
 }
@@ -1136,11 +1127,7 @@ exec_command_encoding(PsqlScanState scan_state)
 		}
 	}
 	else
-	{
-		encoding = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (encoding)
-			free(encoding);
-	}
+		ignore_slash_option(scan_state);
 
 	return true;
 }
@@ -1187,11 +1174,7 @@ exec_command_f(PsqlScanState scan_state)
 		free(fname);
 	}
 	else
-	{
-		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (fname)
-			free(fname);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -1222,11 +1205,7 @@ exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *statu
 		*status = PSQL_CMD_SEND;
 	}
 	else
-	{
-		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (fname)
-			free(fname);
-	}
+		ignore_slash_option(scan_state);
 
 	return true;
 }
@@ -1263,9 +1242,7 @@ exec_command_gset(PsqlScanState scan_state, backslashResult *status)
 		*status = PSQL_CMD_SEND;
 	}
 	else
-	{
-		prefix = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-	}
+		ignore_slash_option(scan_state);
 
 	return true;
 }
@@ -1296,11 +1273,7 @@ exec_command_help(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_line(scan_state);
 
 	return true;
 }
@@ -1351,11 +1324,7 @@ exec_command_include(PsqlScanState scan_state, const char *cmd)
 		}
 	}
 	else
-	{
-		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (fname)
-			free(fname);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -1525,11 +1494,11 @@ exec_command_endif(PsqlScanState scan_state)
 static bool
 exec_command_list(PsqlScanState scan_state, const char *cmd)
 {
-	char	   *pattern;
 	bool		success = true;
 
 	if (is_active_branch(scan_state))
 	{
+		char	   *pattern;
 		bool		show_verbose;
 
 		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
@@ -1538,14 +1507,12 @@ exec_command_list(PsqlScanState scan_state, const char *cmd)
 
 		success = listAllDbs(pattern, show_verbose);
 
+		if (pattern)
+			free(pattern);
 	}
 	else
-	{
-		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-	}
+		ignore_slash_option(scan_state);
 
-	if (pattern)
-		free(pattern);
 
 	return success;
 }
@@ -1615,16 +1582,7 @@ exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *stat
 		free(opt2);
 	}
 	else
-	{
-		opt1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (opt1)
-		{
-			free(opt1);
-			opt2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-			if (opt2)
-				free(opt2);
-		}
-	}
+		ignore_2_slash_options(scan_state);
 
 	return success;
 }
@@ -1646,11 +1604,7 @@ exec_command_out(PsqlScanState scan_state)
 		free(fname);
 	}
 	else
-	{
-		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (fname)
-			free(fname);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -1732,11 +1686,7 @@ exec_command_password(PsqlScanState scan_state)
 		}
 	}
 	else
-	{
-		char	*p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (p)
-			free(p);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -1807,16 +1757,7 @@ exec_command_prompt(PsqlScanState scan_state, const char *cmd)
 		}
 	}
 	else
-	{
-		arg1 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (arg1)
-		{
-			free(arg1);
-			arg2 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-			if (arg2)
-				free(arg2);
-		}
-	}
+		ignore_2_slash_options(scan_state);
 
 	return success;
 }
@@ -1868,17 +1809,7 @@ exec_command_pset(PsqlScanState scan_state)
 		free(opt1);
 	}
 	else
-	{
-		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-
-		if (opt0)
-		{
-			free(opt0);
-			opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-			if (opt1)
-				free(opt1);
-		}
-	}
+		ignore_2_slash_options(scan_state);
 
 	return success;
 }
@@ -1928,11 +1859,7 @@ exec_command_s(PsqlScanState scan_state)
 		free(fname);
 	}
 	else
-	{
-		fname = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (fname)
-			free(fname);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -1983,11 +1910,8 @@ exec_command_set(PsqlScanState scan_state)
 		free(opt0);
 	}
 	else
-	{
-		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (opt0)
-			free(opt0);
-	}
+		while (ignore_slash_option(scan_state))
+			continue;
 
 	return success;
 }
@@ -2041,17 +1965,7 @@ exec_command_setenv(PsqlScanState scan_state, const char *cmd)
 		free(envval);
 	}
 	else
-	{
-		envvar = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-
-		if (envvar)
-		{
-			free(envvar);
-			envval = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-			if (envval)
-				free(envval);
-		}
-	}
+		ignore_2_slash_options(scan_state);
 
 	return success;
 }
@@ -2143,11 +2057,7 @@ exec_command_sf(PsqlScanState scan_state, const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 	else
-	{
-		func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
-		if (func)
-			free(func);
-	}
+		ignore_slash_line(scan_state);
 
 	return true;
 }
@@ -2233,12 +2143,7 @@ exec_command_sv(PsqlScanState scan_state, const char *cmd,
 		destroyPQExpBuffer(view_buf);
 	}
 	else
-	{
-		view = psql_scan_slash_option(scan_state,
-									  OT_WHOLE_LINE, NULL, true);
-		if (view)
-			free(view);
-	}
+		ignore_slash_line(scan_state);
 
 	return true;
 }
@@ -2258,11 +2163,7 @@ exec_command_t(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2282,11 +2183,7 @@ exec_command_T(PsqlScanState scan_state)
 		free(value);
 	}
 	else
-	{
-		value = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (value)
-			free(value);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2316,11 +2213,7 @@ exec_command_timing(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2348,11 +2241,7 @@ exec_command_unset(PsqlScanState scan_state, const char *cmd)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2476,11 +2365,7 @@ exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
 		psql_scan_reset(scan_state);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2500,11 +2385,7 @@ exec_command_x(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2525,11 +2406,7 @@ exec_command_z(PsqlScanState scan_state)
 			free(pattern);
 	}
 	else
-	{
-		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
-		if (pattern)
-			free(pattern);
-	}
+		ignore_slash_option(scan_state);
 
 	return success;
 }
@@ -2549,11 +2426,7 @@ exec_command_shell_escape(PsqlScanState scan_state)
 		free(opt);
 	}
 	else
-	{
-		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
-		if (opt)
-			free(opt);
-	}
+		ignore_slash_line(scan_state);
 
 	return success;
 }
@@ -2576,12 +2449,12 @@ exec_command_slash_command_help(PsqlScanState scan_state)
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
 	}
 	else
-		opt0 = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
-
-	if (opt0)
-		free(opt0);
+		ignore_slash_option(scan_state);
 
 	return true;
 }
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index c9fb1a0..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2824,16 +2824,26 @@ will print anyway #6-2
     \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;
+-- show that vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
 \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
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
 \endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index c812e97..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -470,16 +470,25 @@ deallocate q;
     \echo 'should print #7-4'
 \endif
 
--- show that variables still expand even in false blocks
-\set var 'ab''cd'
--- select :var;
+-- show that vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
 \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
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
 \endif
 
 -- SHOW_CONTEXT
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-26 10:32  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-26 10:32 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Corey,

> v25, try 2:
>
> First file is what you were used to last time. 2nd and 3rd are changes
> since then based on feedback.

Patches do not apply cleanly.
Part 1 gets:
  error: patch failed: src/test/regress/parallel_schedule:89
  error: src/test/regress/parallel_schedule: patch does not apply

There is still the useless file, ok it is removed by part2. Could have 
been just one patch...

After a manual fix in parallel_schedule, make check is ok.

gather_boolean_expression:

ISTM that PQExpBuffer is partially a memory leak. Something should need to 
be freed?

I think that you should use appendPQExpBufferChar and Str instead of 
relying on the format variant which is probably expensive. Something like:

   if (num_options > 0)
     append...Char(buf, ' ');
   append...Str(buf, ...);

is_true_boolean_expression: "return (success) ? tf : false;"
Is this simply: "return success && tf;"?

Some functions have opt1, opt2, but some start at opt0. This does not look 
too consistent, although the inconsistency may be preexisting from your 
patch. Basically, there is a need for some more restructuring in 
"command.c".

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-26 21:42  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-26 21:42 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
> Patches do not apply cleanly.
> Part 1 gets:
>  error: patch failed: src/test/regress/parallel_schedule:89
>  error: src/test/regress/parallel_schedule: patch does not apply
>
> There is still the useless file, ok it is removed by part2. Could have
> been just one patch...
>

parallel_schedule failed because I hadn't rebased recently enough.

git format-patch did us no favors there. New patch is redone as one commit.

ISTM that PQExpBuffer is partially a memory leak. Something should need to
> be freed?
>

I copied that pattern from somewhere else, so yeah, I duplicated whatever
leak was there. Fixed.


> I think that you should use appendPQExpBufferChar and Str instead of
> relying on the format variant which is probably expensive. Something like:
>
>   if (num_options > 0)
>     append...Char(buf, ' ');
>   append...Str(buf, ...);
>

All flavors of appendPQExpBuffer*() I can find have a const *char format
string, so no way to append a naked string. If you know differently, I'm
listening. Not fixed.


>
> is_true_boolean_expression: "return (success) ? tf : false;"
> Is this simply: "return success && tf;"?
>

Neat. Done.


>
> Some functions have opt1, opt2, but some start at opt0. This does not look
> too consistent, although the inconsistency may be preexisting from your
> patch. Basically, there is a need for some more restructuring in
> "command.c".


It is pre-existing. Maybe this patch will inspire someone else to make the
other more consistent.

v26 attached

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001-psql-if-v26.patch (77.6K, ../../CADkLM=dZt6eJQD1pQ7GXUeP83Ut_N+RaRsCgDj+Cpx_bV=LLsg@mail.gmail.com/3-0001-psql-if-v26.patch)
  download | inline diff:
From fc0c466b0331839efe722d089a8ead521e8a827e Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Sun, 26 Mar 2017 17:30:51 -0400
Subject: [PATCH] psql if v26

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1383 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql.out                 |  110 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  109 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 19 files changed, 1699 insertions(+), 284 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..aa9dad4 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,33 +201,158 @@ 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 PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+	enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+	PQExpBuffer exp_buf = createPQExpBuffer();
+	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)))
+	{
+		/* add a space in between subsequent tokens */
+		if (num_options == 0)
+			appendPQExpBuffer(exp_buf, "%s", value);
+		else
+			appendPQExpBuffer(exp_buf, " %s", value);
+		num_options++;
+	}
+
+	/* 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, exp_buf->data);
+	}
+
+	return exp_buf;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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)
+{
+	PQExpBuffer expr = gather_boolean_expression(scan_state, true, true);
+	bool	success = ParseVariableBool(expr->data, action, result);
+	destroyPQExpBuffer(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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +360,78 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +475,19 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +527,67 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+			ignore_slash_option(scan_state);
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +646,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +709,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +732,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +752,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +766,37 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
 		}
-
-		if (pattern)
-			free(pattern);
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+			ignore_slash_option(scan_state);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +829,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +848,25 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+			ignore_2_slash_options(scan_state);
+	}
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +877,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +895,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +911,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +948,33 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+	return true;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +985,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1003,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1016,44 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1082,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1123,17 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1153,41 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1199,34 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,16 +1236,26 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
+	{
+		size_t		len;
+
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
-		size_t		len;
-
 		/* strip any trailing spaces and semicolons */
 		if (opt)
 		{
@@ -973,9 +1269,19 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1289,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,16 +1320,185 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	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;
+	}
+
+	return true;
+}
+
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
+
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
+	}
+
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	/*
+	 * 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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *pattern;
 		bool		show_verbose;
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -1025,15 +1507,24 @@ exec_command(const char *cmd,
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * large object things
-	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
-	{
-		char	   *opt1,
-				   *opt2;
 
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1573,44 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1619,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1682,24 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1753,24 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1805,27 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+	return success;
+}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1833,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1855,22 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1906,25 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+		while (ignore_slash_option(scan_state))
+			continue;
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +1961,23 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +1990,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2053,23 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2082,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2139,62 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2209,22 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2237,31 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2319,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2361,82 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1734,8 +2446,141 @@ exec_command(const char *cmd,
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
+	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
 	}
 
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
+
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,116 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9f95b01..9a9ac1c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index e026b7c..75fa328 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f8e17a..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,115 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 13:53  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-27 13:53 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org



>> ISTM that PQExpBuffer is partially a memory leak. Something should need 
>> to be freed?
>
> I copied that pattern from somewhere else, so yeah, I duplicated whatever
> leak was there.

Hmmm. Indeed some commands do not free, but there is a single use and the 
commands exits afterwards, eg "createuser".

I think that you could use another pattern where you init the 
PQExpBufferData structure instead of create it, so that only the string is 
malloced.

>> I think that you should use appendPQExpBufferChar and Str instead of
>> relying on the format variant which is probably expensive. Something like:
>>
>>   if (num_options > 0)
>>     append...Char(buf, ' ');
>>   append...Str(buf, ...);
>
> All flavors of appendPQExpBuffer*() I can find have a const *char format 
> string, so no way to append a naked string. If you know differently, I'm 
> listening. Not fixed.

These prototypes are from "pqexpbuffer.h", and do not seem to rely on a 
format:

  extern void appendPQExpBufferChar(PQExpBuffer str, char ch);
  extern void appendPQExpBufferStr(PQExpBuffer str, const char *data);

-- 
Fabien


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 14:11  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-27 14:11 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> I think that you could use another pattern where you init the
> PQExpBufferData structure instead of create it, so that only the string is
> malloced.


In v26, I have the functions return PQExpBuffer. The two calling functions
then free it, which should solve any leak.


>
>
> I think that you should use appendPQExpBufferChar and Str instead of
>>> relying on the format variant which is probably expensive. Something
>>> like:
>>>
>>>   if (num_options > 0)
>>>     append...Char(buf, ' ');
>>>   append...Str(buf, ...);
>>>
>>
>> All flavors of appendPQExpBuffer*() I can find have a const *char format
>> string, so no way to append a naked string. If you know differently, I'm
>> listening. Not fixed.
>>
>
> These prototypes are from "pqexpbuffer.h", and do not seem to rely on a
> format:
>
>
Here's an addendum that does that. I can combine them in v27, but figured
this was quicker.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0002-simpler-append.patch (928B, ../../CADkLM=fbEGfea0idN_XsKUG70JmCaMjdejo35q-B5tpC11NTuA@mail.gmail.com/3-0002-simpler-append.patch)
  download | inline diff:
From 2fa083eb0115278f817a2d1d0439a47951a9c48b Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Mon, 27 Mar 2017 10:07:36 -0400
Subject: [PATCH 2/2] simpler append

---
 src/bin/psql/command.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index aa9dad4..a6168df 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -218,10 +218,9 @@ gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
 	while ((value = psql_scan_slash_option(scan_state, slash_opt, NULL, false)))
 	{
 		/* add a space in between subsequent tokens */
-		if (num_options == 0)
-			appendPQExpBuffer(exp_buf, "%s", value);
-		else
-			appendPQExpBuffer(exp_buf, " %s", value);
+		if (num_options > 0)
+			appendPQExpBufferChar(exp_buf, ' ');
+		appendPQExpBufferStr(exp_buf, value);
 		num_options++;
 	}
 
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 14:34  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-27 14:34 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello,

>> I think that you could use another pattern where you init the
>> PQExpBufferData structure instead of create it, so that only the string is
>> malloced.
>
> In v26, I have the functions return PQExpBuffer. The two calling functions
> then free it, which should solve any leak.

Yep, it works as well.

> Here's an addendum that does that. I can combine them in v27, but figured
> this was quicker.

It works.

However having just one full patch with a number would help so that I can 
say "ready to committer" or not on something.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 15:39  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-27 15:39 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Mar 27, 2017 at 10:34 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello,
>
> I think that you could use another pattern where you init the
>>> PQExpBufferData structure instead of create it, so that only the string
>>> is
>>> malloced.
>>>
>>
>> In v26, I have the functions return PQExpBuffer. The two calling functions
>> then free it, which should solve any leak.
>>
>
> Yep, it works as well.
>
> Here's an addendum that does that. I can combine them in v27, but figured
>> this was quicker.
>>
>
> It works.
>
> However having just one full patch with a number would help so that I can
> say "ready to committer" or not on something.
>
> --
> Fabien.
>

And here you go (sorry for the delay, had errands to run).

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] 0001-psql-if-v27.patch (77.6K, ../../CADkLM=czttdEn1fVMb-qAQDvteXU3SOv47GsKbea9+iaYbCK_w@mail.gmail.com/3-0001-psql-if-v27.patch)
  download | inline diff:
From c472548a0b9d782bcde6ddfc142bcabf616a27c7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Mon, 27 Mar 2017 11:37:18 -0400
Subject: [PATCH] psql if v27

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1380 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql.out                 |  110 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  109 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 19 files changed, 1697 insertions(+), 283 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..a6168df 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,33 +201,157 @@ 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 PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+	enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+	PQExpBuffer exp_buf = createPQExpBuffer();
+	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)))
+	{
+		/* add a space in between subsequent tokens */
+		if (num_options > 0)
+			appendPQExpBufferChar(exp_buf, ' ');
+		appendPQExpBufferStr(exp_buf, value);
+		num_options++;
+	}
+
+	/* 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, exp_buf->data);
+	}
+
+	return exp_buf;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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)
+{
+	PQExpBuffer expr = gather_boolean_expression(scan_state, true, true);
+	bool	success = ParseVariableBool(expr->data, action, result);
+	destroyPQExpBuffer(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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +359,78 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +474,19 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +526,67 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+			ignore_slash_option(scan_state);
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +645,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +708,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +731,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +751,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +765,37 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
 		}
-
-		if (pattern)
-			free(pattern);
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+			ignore_slash_option(scan_state);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +828,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +847,25 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+			ignore_2_slash_options(scan_state);
+	}
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +876,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +894,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +910,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +947,33 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+	return true;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +984,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1002,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1015,44 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1081,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1122,17 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1152,41 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1198,34 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,15 +1235,25 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
-		size_t		len;
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
+	{
+		size_t		len;
+
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		/* strip any trailing spaces and semicolons */
 		if (opt)
@@ -973,9 +1268,19 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1288,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,16 +1319,185 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
+
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	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;
+	}
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return true;
+}
+
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
+
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
+	}
+
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	/*
+	 * 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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *pattern;
 		bool		show_verbose;
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -1025,15 +1506,24 @@ exec_command(const char *cmd,
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * large object things
-	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
-	{
-		char	   *opt1,
-				   *opt2;
 
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1572,44 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+		ignore_2_slash_options(scan_state);
+
+	return success;
+}
 
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1618,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1681,24 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1752,24 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1804,27 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+	return success;
+}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1832,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1854,22 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1905,25 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+		while (ignore_slash_option(scan_state))
+			continue;
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +1960,23 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +1989,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2052,23 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2081,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2138,62 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2208,22 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2236,31 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2318,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2360,82 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1734,8 +2445,141 @@ exec_command(const char *cmd,
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
+	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
 	}
 
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
+
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,116 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9f95b01..9a9ac1c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index e026b7c..75fa328 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f8e17a..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,115 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 19:25  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-27 19:25 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> And here you go

Patch applies cleany, make check ok. Looks pretty good.

A minor detail I have just noticed, sorry: now that options are discarded 
by functions, some string variable declarations should be moved back 
inside the active branch. You moved them out because you where sharing the 
variables between the active & inactive branches, but this is no longer 
necessary, and the project practice seems to declare variables just where 
they are needed. That would be pattern in d, encoding in encoding, fname 
in f and g and include and out and s, prefix in gset, opt in help, opt* in 
lo and pset and set, arg* in prompt, env* in setenv... and maybe a few 
others.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 19:51  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-27 19:51 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Mar 27, 2017 at 3:25 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> And here you go
>>
>
> Patch applies cleany, make check ok. Looks pretty good.
>
> A minor detail I have just noticed, sorry: now that options are discarded
> by functions, some string variable declarations should be moved back inside
> the active branch. You moved them out because you where sharing the
> variables between the active & inactive branches, but this is no longer
> necessary, and the project practice seems to declare variables just where
> they are needed. That would be pattern in d, encoding in encoding, fname in
> f and g and include and out and s, prefix in gset, opt in help, opt* in lo
> and pset and set, arg* in prompt, env* in setenv... and maybe a few others.
>
> --
> Fabien.
>

done:
encoding f g gset help include lo out prompt pset s set setenv sf sv t T
timing unset watch x z ! ?

weird cases where they're both still needed:
d write

0001+0002 patch primarily for ease of review. will be following with a
single v28 patch shortly.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] 0001-psql-if-v27.patch (77.6K, ../../CADkLM=f6ELXZOmW9UU3vyYu3jzTbh2o2178r7+wt2emN+DWmig@mail.gmail.com/3-0001-psql-if-v27.patch)
  download | inline diff:
From c472548a0b9d782bcde6ddfc142bcabf616a27c7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Mon, 27 Mar 2017 11:37:18 -0400
Subject: [PATCH 1/2] psql if v27

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1380 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql.out                 |  110 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  109 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 19 files changed, 1697 insertions(+), 283 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..a6168df 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,33 +201,157 @@ 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 PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+	enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+	PQExpBuffer exp_buf = createPQExpBuffer();
+	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)))
+	{
+		/* add a space in between subsequent tokens */
+		if (num_options > 0)
+			appendPQExpBufferChar(exp_buf, ' ');
+		appendPQExpBufferStr(exp_buf, value);
+		num_options++;
+	}
+
+	/* 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, exp_buf->data);
+	}
+
+	return exp_buf;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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)
+{
+	PQExpBuffer expr = gather_boolean_expression(scan_state, true, true);
+	bool	success = ParseVariableBool(expr->data, action, result);
+	destroyPQExpBuffer(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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +359,78 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +474,19 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +526,67 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+			ignore_slash_option(scan_state);
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +645,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +708,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +731,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +751,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +765,37 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
 		}
-
-		if (pattern)
-			free(pattern);
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+			ignore_slash_option(scan_state);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +828,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +847,25 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+			ignore_2_slash_options(scan_state);
+	}
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +876,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +894,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +910,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +947,33 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+	return true;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +984,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1002,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1015,44 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1081,25 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+	char	   *encoding;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1122,17 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1152,41 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	char	*fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1198,34 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	char	   *prefix;
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,15 +1235,25 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
-		size_t		len;
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+	char	   *opt;
+
+	if (is_active_branch(scan_state))
+	{
+		size_t		len;
+
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		/* strip any trailing spaces and semicolons */
 		if (opt)
@@ -973,9 +1268,19 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1288,20 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,16 +1319,185 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
+
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	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;
+	}
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return true;
+}
+
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
+
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
+	}
+
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	/*
+	 * 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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *pattern;
 		bool		show_verbose;
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -1025,15 +1506,24 @@ exec_command(const char *cmd,
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * large object things
-	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
-	{
-		char	   *opt1,
-				   *opt2;
 
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+	char   *opt1, *opt2;
+
+	if (is_active_branch(scan_state))
+	{
 		opt1 = psql_scan_slash_option(scan_state,
 									  OT_NORMAL, NULL, true);
 		opt2 = psql_scan_slash_option(scan_state,
@@ -1082,26 +1572,44 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+		ignore_2_slash_options(scan_state);
+
+	return success;
+}
 
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1618,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,14 +1681,24 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+	char	   *arg1,
+			   *arg2;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
 
 		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
@@ -1225,14 +1752,24 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0,
+		   *opt1;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1804,27 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+	return success;
+}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1832,19 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *fname;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1854,22 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt0;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1905,25 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+		while (ignore_slash_option(scan_state))
+			continue;
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *envvar;
+	char   *envval;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,13 +1960,23 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *func;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
 
 		func_buf = createPQExpBuffer();
@@ -1400,22 +1989,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,13 +2052,23 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *view;
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
 
 		view_buf = createPQExpBuffer();
@@ -1482,22 +2081,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2138,62 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,11 +2208,22 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
+		opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, false);
 
 		if (!opt)
@@ -1596,18 +2236,31 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2318,33 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2360,82 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *pattern;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	char   *opt0 = NULL;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1734,8 +2445,141 @@ exec_command(const char *cmd,
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
+	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
 	}
 
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
+
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,116 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9f95b01..9a9ac1c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index e026b7c..75fa328 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f8e17a..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,115 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



  [application/octet-stream] 0002-fix-var-scoping.patch (10.3K, ../../CADkLM=f6ELXZOmW9UU3vyYu3jzTbh2o2178r7+wt2emN+DWmig@mail.gmail.com/4-0002-fix-var-scoping.patch)
  download | inline diff:
From 22de9fbff084a482a5991191c8723a06d110e99f Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Mon, 27 Mar 2017 15:48:19 -0400
Subject: [PATCH 2/2] fix var scoping

---
 src/bin/psql/command.c | 87 ++++++++++++++++----------------------------------
 1 file changed, 28 insertions(+), 59 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a6168df..98817eb 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1095,11 +1095,10 @@ exec_command_echo(PsqlScanState scan_state, const char *cmd)
 static bool
 exec_command_encoding(PsqlScanState scan_state)
 {
-	char	   *encoding;
 
 	if (is_active_branch(scan_state))
 	{
-		encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char   *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -1160,11 +1159,10 @@ static bool
 exec_command_f(PsqlScanState scan_state)
 {
 	bool		success = true;
-	char	   *fname;
 
 	if (is_active_branch(scan_state))
 	{
-		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
@@ -1182,11 +1180,10 @@ exec_command_f(PsqlScanState scan_state)
 static bool
 exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
 {
-	char	*fname;
 
 	if (is_active_branch(scan_state))
 	{
-		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
+		char	*fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -1222,10 +1219,9 @@ exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
 static bool
 exec_command_gset(PsqlScanState scan_state, backslashResult *status)
 {
-	char	   *prefix;
 	if (is_active_branch(scan_state))
 	{
-		prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -1247,13 +1243,11 @@ exec_command_gset(PsqlScanState scan_state, backslashResult *status)
 static bool
 exec_command_help(PsqlScanState scan_state)
 {
-	char	   *opt;
 
 	if (is_active_branch(scan_state))
 	{
 		size_t		len;
-
-		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		char	   *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		/* strip any trailing spaces and semicolons */
 		if (opt)
@@ -1297,11 +1291,10 @@ static bool
 exec_command_include(PsqlScanState scan_state, const char *cmd)
 {
 	bool		success = true;
-	char	   *fname;
 
 	if (is_active_branch(scan_state))
 	{
-		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1520,14 +1513,11 @@ static bool
 exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
 {
 	bool	success = true;
-	char   *opt1, *opt2;
 
 	if (is_active_branch(scan_state))
 	{
-		opt1 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
-		opt2 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (strcmp(cmd + 3, "export") == 0)
 		{
@@ -1589,11 +1579,10 @@ static bool
 exec_command_out(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *fname;
 
 	if (is_active_branch(scan_state))
 	{
-		fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
@@ -1692,16 +1681,13 @@ static bool
 exec_command_prompt(PsqlScanState scan_state, const char *cmd)
 {
 	bool		success = true;
-	char	   *arg1,
-			   *arg2;
 
 	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-
-		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!arg1)
 		{
@@ -1763,13 +1749,11 @@ static bool
 exec_command_pset(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt0,
-		   *opt1;
 
 	if (is_active_branch(scan_state))
 	{
-		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-		opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1840,11 +1824,10 @@ static bool
 exec_command_s(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *fname;
 
 	if (is_active_branch(scan_state))
 	{
-		fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1865,11 +1848,10 @@ static bool
 exec_command_set(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt0;
 
 	if (is_active_branch(scan_state))
 	{
-		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1917,13 +1899,11 @@ static bool
 exec_command_setenv(PsqlScanState scan_state, const char *cmd)
 {
 	bool	success = true;
-	char   *envvar;
-	char   *envval;
 
 	if (is_active_branch(scan_state))
 	{
-		envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-		envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1971,13 +1951,13 @@ static bool
 exec_command_sf(PsqlScanState scan_state, const char *cmd,
 				backslashResult *status)
 {
-	char	   *func;
 
 	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
 		Oid			foid = InvalidOid;
+		char	   *func;
 
 		func_buf = createPQExpBuffer();
 		func = psql_scan_slash_option(scan_state,
@@ -2063,13 +2043,13 @@ static bool
 exec_command_sv(PsqlScanState scan_state, const char *cmd,
 				backslashResult *status)
 {
-	char	   *view;
 
 	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
 		Oid			view_oid = InvalidOid;
+		char	   *view;
 
 		view_buf = createPQExpBuffer();
 		view = psql_scan_slash_option(scan_state,
@@ -2149,11 +2129,10 @@ static bool
 exec_command_t(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
-		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
@@ -2169,11 +2148,10 @@ static bool
 exec_command_T(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *value;
 
 	if (is_active_branch(scan_state))
 	{
-		value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
@@ -2189,11 +2167,10 @@ static bool
 exec_command_timing(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
-		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -2219,12 +2196,10 @@ static bool
 exec_command_unset(PsqlScanState scan_state, const char *cmd)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
-		opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt)
 		{
@@ -2337,13 +2312,12 @@ static bool
 exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
 		double		sleep = 2;
 
-		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		/* Convert optional sleep-length argument */
 		if (opt)
@@ -2371,11 +2345,10 @@ static bool
 exec_command_x(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
-		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
@@ -2391,11 +2364,10 @@ static bool
 exec_command_z(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *pattern;
 
 	if (is_active_branch(scan_state))
 	{
-		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
@@ -2412,11 +2384,10 @@ static bool
 exec_command_shell_escape(PsqlScanState scan_state)
 {
 	bool	success = true;
-	char   *opt;
 
 	if (is_active_branch(scan_state))
 	{
-		opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
@@ -2431,11 +2402,9 @@ exec_command_shell_escape(PsqlScanState scan_state)
 static bool
 exec_command_slash_command_help(PsqlScanState scan_state)
 {
-	char   *opt0 = NULL;
-
 	if (is_active_branch(scan_state))
 	{
-		opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 19:53  Corey Huinker <corey.huinker@gmail.com>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 2 replies; 232+ messages in thread

From: Corey Huinker @ 2017-03-27 19:53 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

>
>
> 0001+0002 patch primarily for ease of review. will be following with a
> single v28 patch shortly.
>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] 0001-psql-if-v28.patch (77.9K, ../../CADkLM=eUksHSUc_HXE8CRFgj85C8cABVfVY1hRoTfdNvwwOUGg@mail.gmail.com/3-0001-psql-if-v28.patch)
  download | inline diff:
From 4293f405864082e0f091d7e5360b40ec7926e542 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Mon, 27 Mar 2017 15:52:17 -0400
Subject: [PATCH] psql if v28

---
 doc/src/sgml/ref/psql-ref.sgml                     |   90 +-
 src/bin/psql/.gitignore                            |    2 +
 src/bin/psql/Makefile                              |    4 +-
 src/bin/psql/command.c                             | 1365 ++++++++++++++++----
 src/bin/psql/common.c                              |    4 +-
 src/bin/psql/conditional.c                         |  103 ++
 src/bin/psql/conditional.h                         |   62 +
 src/bin/psql/copy.c                                |    4 +-
 src/bin/psql/help.c                                |    7 +
 src/bin/psql/mainloop.c                            |   54 +-
 src/bin/psql/prompt.c                              |    6 +-
 src/bin/psql/prompt.h                              |    3 +-
 src/bin/psql/startup.c                             |    8 +-
 src/test/regress/expected/psql.out                 |  110 ++
 .../regress/expected/psql_if_on_error_stop.out     |   14 +
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 src/test/regress/sql/psql.sql                      |  109 ++
 src/test/regress/sql/psql_if_on_error_stop.sql     |   17 +
 19 files changed, 1674 insertions(+), 291 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h
 create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out
 create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..98817eb 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,33 +201,157 @@ 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 PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+	enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+	PQExpBuffer exp_buf = createPQExpBuffer();
+	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)))
+	{
+		/* add a space in between subsequent tokens */
+		if (num_options > 0)
+			appendPQExpBufferChar(exp_buf, ' ');
+		appendPQExpBufferStr(exp_buf, value);
+		num_options++;
+	}
+
+	/* 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, exp_buf->data);
+	}
+
+	return exp_buf;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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)
+{
+	PQExpBuffer expr = gather_boolean_expression(scan_state, true, true);
+	bool	success = ParseVariableBool(expr->data, action, result);
+	destroyPQExpBuffer(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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +359,78 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +474,19 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +526,67 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+			ignore_slash_option(scan_state);
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +645,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +708,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +731,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +751,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +765,37 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
 		}
-
-		if (pattern)
-			free(pattern);
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+			ignore_slash_option(scan_state);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +828,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +847,25 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+			ignore_2_slash_options(scan_state);
+	}
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +876,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +894,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +910,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +947,33 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+	return true;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +984,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1002,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1015,44 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1081,24 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		char   *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1121,17 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1151,39 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		char	   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		char	*fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1195,33 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		char	   *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,15 +1231,23 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
-		size_t		len;
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+
+	if (is_active_branch(scan_state))
+	{
+		size_t		len;
+		char	   *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		/* strip any trailing spaces and semicolons */
 		if (opt)
@@ -973,9 +1262,19 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1282,19 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
+
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,16 +1312,185 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	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;
+	}
+
+	return true;
+}
+
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
+
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
+	}
+
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	/*
+	 * 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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *pattern;
 		bool		show_verbose;
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -1025,19 +1499,25 @@ exec_command(const char *cmd,
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * large object things
-	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
-	{
-		char	   *opt1,
-				   *opt2;
 
-		opt1 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
-		opt2 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
+	{
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (strcmp(cmd + 3, "export") == 0)
 		{
@@ -1082,26 +1562,43 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1607,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,17 +1670,24 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
-
-		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!arg1)
 		{
@@ -1225,14 +1738,22 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1788,27 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+	return success;
+}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1816,18 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1837,21 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1887,23 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+		while (ignore_slash_option(scan_state))
+			continue;
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,14 +1940,24 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
+		char	   *func;
 
 		func_buf = createPQExpBuffer();
 		func = psql_scan_slash_option(scan_state,
@@ -1400,22 +1969,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,14 +2032,24 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
+		char	   *view;
 
 		view_buf = createPQExpBuffer();
 		view = psql_scan_slash_option(scan_state,
@@ -1482,22 +2061,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2118,59 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,12 +2185,21 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt)
 		{
@@ -1596,18 +2211,31 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2293,32 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2334,77 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1734,8 +2414,141 @@ exec_command(const char *cmd,
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
+	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
 	}
 
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
+
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,116 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out
new file mode 100644
index 0000000..c9dd3c7
--- /dev/null
+++ b/src/test/regress/expected/psql_if_on_error_stop.out
@@ -0,0 +1,14 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+ok
+\else
+	\echo error at 1.1
+\elif
+\elif: cannot occur after \else
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9f95b01..9a9ac1c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext psql_if_on_error_stop
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index e026b7c..75fa328 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,6 +124,7 @@ test: alter_generic
 test: alter_operator
 test: misc
 test: psql
+test: psql_if_on_error_stop
 test: async
 test: dbsize
 test: misc_functions
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f8e17a..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,115 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql
new file mode 100644
index 0000000..864ebac
--- /dev/null
+++ b/src/test/regress/sql/psql_if_on_error_stop.sql
@@ -0,0 +1,17 @@
+--
+-- Test psql \if-errors respecting ON_ERROR_STOP
+--
+-- Tests for psql features that aren't closely connected to any
+-- specific server features
+--
+\set ON_ERROR_STOP on
+\if true
+	\echo ok
+\else
+	\echo error at 1.1
+\elif
+	\echo error at 1.2
+\else
+	\echo error at 1.3
+\endif
+\echo error at 1.4
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-27 21:34  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-27 21:34 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


>> 0001+0002 patch primarily for ease of review. will be following with a
>> single v28 patch shortly.

Applies cleanly. Make check ok. I think it behaves as committers required 
last. Let us try again with them...

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 00:52  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Corey Huinker <corey.huinker@gmail.com>
  1 sibling, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-29 00:52 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Corey Huinker <corey.huinker@gmail.com> writes:
[ 0001-psql-if-v28.patch ]

Starting to look at this version, and what jumped out at me in testing
is that the regression output looks like this:

parallel group (12 tests):  psql_if_on_error_stop dbsize async misc_functions tidscan alter_operator tsrf psql alter_generic misc stats_ext sysviews
     alter_generic            ... ok
     alter_operator           ... ok
     misc                     ... ok
     psql                     ... ok
     psql_if_on_error_stop    ... ok (test process exited with exit code 3)
     async                    ... ok
     dbsize                   ... ok
     misc_functions           ... ok
     sysviews                 ... ok
     tsrf                     ... ok
     tidscan                  ... ok
     stats_ext                ... ok

Don't think we can have that.  Even if pg_regress considers it a success,
every hacker is going to have to learn that that's a "pass", and I don't
think I want to be answering that question every week till kingdom come.

I'm not really sure we need a test for this behavior.  If we're
sufficiently dead set on it, we could go back to the TAP-based approach,
but I still doubt that this test is worth the amount of overhead that
would add.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 04:18  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-29 04:18 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

> psql_if_on_error_stop    ... ok (test process exited with exit code 3)
>
> Don't think we can have that.  Even if pg_regress considers it a success,
> every hacker is going to have to learn that that's a "pass",

Well, it says "ok"...

> and I don't think I want to be answering that question every week till 
> kingdom come.

Hmmm.

What if the test is renamed, say "psql_if_exit_code_3"? Maybe the clue 
would be clear enough to avoid questions? Or just remove the exit message 
but check the exit code is as expected?

> I'm not really sure we need a test for this behavior.

My 0.02€:

I have learned the hard way over the years that what is not tested does 
not really work, including error behaviors. These tests (well, the initial 
TAP version at least) helped debug the feature, and would help keeping it 
alive when the code is updated.

Now if you do not want this test, it can be removed. The feature is worthy 
even without it.

> If we're sufficiently dead set on it, we could go back to the TAP-based 
> approach,

Hmmm. You rejected it. I agree that TAP tests are not well suited for some 
simple tests because of their initdb overhead.

> but I still doubt that this test is worth the amount of overhead that 
> would add.

I think that there is an underlying issue with keeping on rejecting tests 
which aim at having a reasonable code coverage of features by exercising 
different paths.

Maybe there could be some "larger but still reasonable tests" activated on 
demand so as to being able to keep tests and run them from time to time, 
which would not interfere too much with committers' work, and that some 
farm animals would run?

I thought that was one of the purpose of TAP tests, but obviously it is 
not.

-- 
Fabien.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 14:14  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 232+ messages in thread

From: Tom Lane @ 2017-03-29 14:14 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> If we're sufficiently dead set on it, we could go back to the TAP-based 
>> approach,

> Hmmm. You rejected it. I agree that TAP tests are not well suited for some 
> simple tests because of their initdb overhead.

>> but I still doubt that this test is worth the amount of overhead that 
>> would add.

> I think that there is an underlying issue with keeping on rejecting tests 
> which aim at having a reasonable code coverage of features by exercising 
> different paths.

There's certainly a fair amount of psql behavior that's not adequately
testable within the standard regression test infrastructure.  Parsing of
command line arguments and exit codes for unusual cases both fall into
that area, and then there's things like prompts and tab completion.
If someone were to put together a TAP test suite that covered all that
and made for a meaningful improvement in psql's altogether-miserable
code coverage report[1], I would think that that would be a useful
expenditure of buildfarm time.  What I'm objecting to is paying the
overhead for such a suite in order to test just this one thing.  I don't
think that that passes the bang-for-buck test; or in other words, this
isn't the place I would start if I were creating a TAP suite for psql.

			regards, tom lane

[1] https://coverage.postgresql.org/src/bin/psql/index.html


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 18:16  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-29 18:16 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


Hello Tom,

> If someone were to put together a TAP test suite that covered all that
> and made for a meaningful improvement in psql's altogether-miserable
> code coverage report[1], I would think that that would be a useful
> expenditure of buildfarm time.

Ok, this is an interesting point.

> What I'm objecting to is paying the overhead for such a suite in order 
> to test just this one thing.

Well, it should start somewhere. Once something is running it is easier to 
add more tests.

> think that that passes the bang-for-buck test; or in other words, this
> isn't the place I would start if I were creating a TAP suite for psql.

Sure, I would not have started with that either.

Note that from this patch point of view, it is somehow logical to start 
testing a given feature when this very feature is being developed...

The summary is that we agree that psql test coverage is abysmal, but you 
do not want to bootstrap a better test infrastructure for this particular 
and rather special new feature. Ok.

Maybe Corey can submit another patch with the exit 3 test removed.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 19:34  Corey Huinker <corey.huinker@gmail.com>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 232+ messages in thread

From: Corey Huinker @ 2017-03-29 19:34 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

New Patch v29: Now with less coverage!
(same as v28 minus the psql-on-error-stop.sql and associated changes)
Fabien raises some good points about if/then being a tremendous tool for
enhancing other existing regression tests.


On Wed, Mar 29, 2017 at 2:16 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

>
> Hello Tom,
>
> If someone were to put together a TAP test suite that covered all that
>> and made for a meaningful improvement in psql's altogether-miserable
>> code coverage report[1], I would think that that would be a useful
>> expenditure of buildfarm time.
>>
>
> Ok, this is an interesting point.
>
> What I'm objecting to is paying the overhead for such a suite in order to
>> test just this one thing.
>>
>
> Well, it should start somewhere. Once something is running it is easier to
> add more tests.
>
> think that that passes the bang-for-buck test; or in other words, this
>> isn't the place I would start if I were creating a TAP suite for psql.
>>
>
> Sure, I would not have started with that either.
>
> Note that from this patch point of view, it is somehow logical to start
> testing a given feature when this very feature is being developed...
>
> The summary is that we agree that psql test coverage is abysmal, but you
> do not want to bootstrap a better test infrastructure for this particular
> and rather special new feature. Ok.
>
> Maybe Corey can submit another patch with the exit 3 test removed.
>
> --
> Fabien.
>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001-psql-if-v29.patch (75.2K, ../../CADkLM=cbF9yLTeU6T_sMxrrpZhxQH_Y+bNXdOFiGn8oVXjBngg@mail.gmail.com/3-0001-psql-if-v29.patch)
  download | inline diff:
From 9caa338fa085bc1c0ee16f22c0c1c8d3c8fc1697 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@moat.com>
Date: Wed, 29 Mar 2017 15:30:22 -0400
Subject: [PATCH] psql if v29

---
 doc/src/sgml/ref/psql-ref.sgml     |   90 ++-
 src/bin/psql/.gitignore            |    2 +
 src/bin/psql/Makefile              |    4 +-
 src/bin/psql/command.c             | 1365 ++++++++++++++++++++++++++++--------
 src/bin/psql/common.c              |    4 +-
 src/bin/psql/conditional.c         |  103 +++
 src/bin/psql/conditional.h         |   62 ++
 src/bin/psql/copy.c                |    4 +-
 src/bin/psql/help.c                |    7 +
 src/bin/psql/mainloop.c            |   54 +-
 src/bin/psql/prompt.c              |    6 +-
 src/bin/psql/prompt.h              |    3 +-
 src/bin/psql/startup.c             |    8 +-
 src/test/regress/expected/psql.out |  110 +++
 src/test/regress/sql/psql.sql      |  109 +++
 15 files changed, 1641 insertions(+), 290 deletions(-)
 create mode 100644 src/bin/psql/conditional.c
 create mode 100644 src/bin/psql/conditional.h

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..18f8bfe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,93 @@ 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 a warning and be treated as false.
+        </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>
+-- 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 +3802,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..cfc4972 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)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..98817eb 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,33 +201,157 @@ 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 PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+	enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+	PQExpBuffer exp_buf = createPQExpBuffer();
+	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)))
+	{
+		/* add a space in between subsequent tokens */
+		if (num_options > 0)
+			appendPQExpBufferChar(exp_buf, ' ');
+		appendPQExpBufferStr(exp_buf, value);
+		num_options++;
+	}
+
+	/* 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, exp_buf->data);
+	}
+
+	return exp_buf;
+}
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * Read a boolean expression, but do nothing with it.
  */
-static backslashResult
-exec_command(const char *cmd,
-			 PsqlScanState scan_state,
-			 PQExpBuffer query_buf)
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
 {
-	bool		success = true; /* indicate here if the command ran ok or
-								 * failed */
-	backslashResult status = PSQL_CMD_SKIP_LINE;
+	destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false));
+}
 
-	/*
-	 * \a -- toggle field alignment This makes little sense but we keep it
-	 * around.
-	 */
-	if (strcmp(cmd, "a") == 0)
+/*
+ * 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)
+{
+	PQExpBuffer expr = gather_boolean_expression(scan_state, true, true);
+	bool	success = ParseVariableBool(expr->data, action, result);
+	destroyPQExpBuffer(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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Convenience routine to indicate if the current branch is active
+ */
+static bool
+is_active_branch(PsqlScanState scan_state)
+{
+	return conditional_active(get_conditional_stack(scan_state));
+}
+
+/*
+ * Ignore exactly one slash command option. Return true if an option was found
+ */
+static bool
+ignore_slash_option(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false);
+	if (!p)
+		return false;
+	free(p);
+	return true;
+}
+
+/*
+ * Ignore up to two slash command options.
+ */
+static void
+ignore_2_slash_options(PsqlScanState scan_state)
+{
+	if (ignore_slash_option(scan_state))
+		ignore_slash_option(scan_state);
+}
+
+/*
+ * Ignore exactly one whole-line slash command option.
+ */
+static void
+ignore_slash_line(PsqlScanState scan_state)
+{
+	char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
+	if (p)
+		free(p);
+}
+
+/*
+ * \a -- toggle field alignment This makes little sense but we keep it
+ * around.
+ */
+static bool
+exec_command_a(PsqlScanState scan_state)
+{
+	bool success = true;
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_ALIGNED)
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 		else
 			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
 	}
+	return success;
+}
 
-	/* \C -- override table title (formerly change HTML caption) */
-	else if (strcmp(cmd, "C") == 0)
+/* \C -- override table title (formerly change HTML caption) */
+static bool
+exec_command_C(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -225,61 +359,78 @@ exec_command(const char *cmd,
 		success = do_pset("title", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \c or \connect -- connect to database using the specified parameters.
-	 *
-	 * \c [-reuse-previous=BOOL] dbname user host port
-	 *
-	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
-	 *
-	 * \c - - hst		Connect to current database on current port of host
-	 * "hst" as current user. \c - usr - prt   Connect to current database on
-	 * "prt" port of current host as user "usr". \c dbs			  Connect to
-	 * "dbs" database on current port of current host as current user.
-	 */
-	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+	return success;
+}
+
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst		Connect to current database on current port of host
+ * "hst" as current user. \c - usr - prt   Connect to current database on
+ * "prt" port of current host as user "usr". \c dbs			  Connect to
+ * "dbs" database on current port of current host as current user.
+ */
+static bool
+exec_command_connect(PsqlScanState scan_state)
+{
+	static const char prefix[] = "-reuse-previous=";
+	char	   *opt1,
+			   *opt2,
+			   *opt3,
+			   *opt4;
+	enum trivalue reuse_previous = TRI_DEFAULT;
+	bool	success = true;
+	bool	active_branch = is_active_branch(scan_state);
+
+	opt1 = read_connect_arg(scan_state);
+	if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
 	{
-		static const char prefix[] = "-reuse-previous=";
-		char	   *opt1,
-				   *opt2,
-				   *opt3,
-				   *opt4;
-		enum trivalue reuse_previous = TRI_DEFAULT;
+		bool		on_off;
 
-		opt1 = read_connect_arg(scan_state);
-		if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+		success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+									((active_branch) ?
+										"-reuse-previous" : NULL),
+									&on_off);
+		if (success)
 		{
-			bool		on_off;
-
-			success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
-										"-reuse-previous",
-										&on_off);
-			if (success)
-			{
-				reuse_previous = on_off ? TRI_YES : TRI_NO;
-				free(opt1);
-				opt1 = read_connect_arg(scan_state);
-			}
+			reuse_previous = on_off ? TRI_YES : TRI_NO;
+			free(opt1);
+			opt1 = read_connect_arg(scan_state);
 		}
+	}
 
-		if (success)			/* give up if reuse_previous was invalid */
-		{
-			opt2 = read_connect_arg(scan_state);
-			opt3 = read_connect_arg(scan_state);
-			opt4 = read_connect_arg(scan_state);
+	if (success)			/* give up if reuse_previous was invalid */
+	{
+		opt2 = read_connect_arg(scan_state);
+		opt3 = read_connect_arg(scan_state);
+		opt4 = read_connect_arg(scan_state);
 
+		if (active_branch)
 			success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
-			free(opt2);
-			free(opt3);
-			free(opt4);
-		}
-		free(opt1);
+		free(opt2);
+		free(opt3);
+		free(opt4);
 	}
+	free(opt1);
+	return success;
+}
 
-	/* \cd */
-	else if (strcmp(cmd, "cd") == 0)
+/* \cd */
+static bool
+exec_command_cd(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt = psql_scan_slash_option(scan_state,
 												 OT_NORMAL, NULL, true);
@@ -323,9 +474,19 @@ exec_command(const char *cmd,
 		if (opt)
 			free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \conninfo -- display information about the current connection */
-	else if (strcmp(cmd, "conninfo") == 0)
+	return success;
+}
+
+/* \conninfo -- display information about the current connection */
+static bool
+exec_command_conninfo(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *db = PQdb(pset.db);
 
@@ -365,37 +526,67 @@ exec_command(const char *cmd,
 			PQconninfoFree(connOptions);
 		}
 	}
+	return success;
+}
 
-	/* \copy */
-	else if (pg_strcasecmp(cmd, "copy") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+/* \copy */
+static bool
+exec_command_copy(PsqlScanState scan_state)
+{
+	bool	success = true;
+	char   *opt = psql_scan_slash_option(scan_state,
+											 OT_WHOLE_LINE, NULL, false);
 
+	if (is_active_branch(scan_state))
 		success = do_copy(opt);
-		free(opt);
-	}
+	free(opt);
 
-	/* \copyright */
-	else if (strcmp(cmd, "copyright") == 0)
+	return success;
+}
+
+
+/* \copyright */
+static bool
+exec_command_copyright(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 		print_copyright();
+	return true;
+}
 
-	/* \crosstabview -- execute a query and display results in crosstab */
-	else if (strcmp(cmd, "crosstabview") == 0)
-	{
-		int			i;
 
+/* \crosstabview -- execute a query and display results in crosstab */
+static bool
+exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status)
+{
+	int		i;
+
+	if (is_active_branch(scan_state))
+	{
 		for (i = 0; i < lengthof(pset.ctv_args); i++)
 			pset.ctv_args[i] = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
 		pset.crosstab_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
+	}
+	else
+	{
+		for (i = 0; i < lengthof(pset.ctv_args); i++)
+			ignore_slash_option(scan_state);
 	}
+	return true;
+}
 
-	/* \d* commands */
-	else if (cmd[0] == 'd')
+/* \d* commands */
+static bool
+exec_command_d(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+	char	   *pattern = NULL;
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern;
 		bool		show_verbose,
 					show_system;
 
@@ -454,7 +645,7 @@ exec_command(const char *cmd,
 						success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -517,7 +708,7 @@ exec_command(const char *cmd,
 						success = describeSubscriptions(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 				}
 				break;
 			case 'u':
@@ -540,7 +731,7 @@ exec_command(const char *cmd,
 						success = listTSTemplates(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -560,7 +751,7 @@ exec_command(const char *cmd,
 						success = listForeignTables(pattern, show_verbose);
 						break;
 					default:
-						status = PSQL_CMD_UNKNOWN;
+						*status = PSQL_CMD_UNKNOWN;
 						break;
 				}
 				break;
@@ -574,24 +765,37 @@ exec_command(const char *cmd,
 				success = listEventTriggers(pattern, show_verbose);
 				break;
 			default:
-				status = PSQL_CMD_UNKNOWN;
+				*status = PSQL_CMD_UNKNOWN;
 		}
-
-		if (pattern)
-			free(pattern);
+	}
+	else
+	{
+		/* digest and discard options as appropriate */
+		pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true);
+		if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's')
+			ignore_slash_option(scan_state);
 	}
 
+	if (pattern)
+		free(pattern);
 
-	/*
-	 * \e or \edit -- edit the current query buffer, or edit a file and make
-	 * it the query buffer
-	 */
-	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+	return success;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
+ */
+static bool
+exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -624,18 +828,18 @@ exec_command(const char *cmd,
 				if (lineno < 1)
 				{
 					psql_error("invalid line number: %s\n", ln);
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 				}
 			}
-			if (status != PSQL_CMD_ERROR)
+			if (*status != PSQL_CMD_ERROR)
 			{
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
 				if (do_edit(fname, query_buf, lineno, NULL))
-					status = PSQL_CMD_NEWEDIT;
+					*status = PSQL_CMD_NEWEDIT;
 				else
-					status = PSQL_CMD_ERROR;
+					*status = PSQL_CMD_ERROR;
 			}
 			if (fname)
 				free(fname);
@@ -643,12 +847,25 @@ exec_command(const char *cmd,
 				free(ln);
 		}
 	}
+	else
+	{
+		if (query_buf)
+			ignore_2_slash_options(scan_state);
+	}
 
-	/*
-	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
-	 * template if no argument is given
-	 */
-	else if (strcmp(cmd, "ef") == 0)
+	return true;
+}
+
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+static bool
+exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -659,12 +876,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -677,7 +894,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!func)
 			{
@@ -693,12 +910,12 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableFunction, func, &foid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableFunction, foid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (lineno > 0)
 			{
@@ -730,24 +947,33 @@ exec_command(const char *cmd,
 				free(func);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/*
-	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
-	 * no argument is given
-	 */
-	else if (strcmp(cmd, "ev") == 0)
+	return true;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW template if
+ * no argument is given
+ */
+static bool
+exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf,
+					backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		int			lineno = -1;
 
@@ -758,12 +984,12 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support editing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -776,7 +1002,7 @@ exec_command(const char *cmd,
 			if (lineno == 0)
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!view)
 			{
@@ -789,35 +1015,44 @@ exec_command(const char *cmd,
 			else if (!lookup_object_oid(EditableView, view, &view_oid))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 			else if (!get_create_object_cmd(EditableView, view_oid, query_buf))
 			{
 				/* error already reported */
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			}
 
 			if (view)
 				free(view);
 		}
 
-		if (status != PSQL_CMD_ERROR)
+		if (*status != PSQL_CMD_ERROR)
 		{
 			bool		edited = false;
 
 			if (!do_edit(NULL, query_buf, lineno, &edited))
-				status = PSQL_CMD_ERROR;
+				*status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
 			else
-				status = PSQL_CMD_NEWEDIT;
+				*status = PSQL_CMD_NEWEDIT;
 		}
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \echo and \qecho */
-	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+	return true;
+}
+
+/* \echo and \qecho */
+static bool
+exec_command_echo(PsqlScanState scan_state, const char *cmd)
+{
+	char	   *value;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value;
 		char		quoted;
 		bool		no_newline = false;
 		bool		first = true;
@@ -846,12 +1081,24 @@ exec_command(const char *cmd,
 		if (!no_newline)
 			fputs("\n", fout);
 	}
+	else
+	{
+		while ((value = psql_scan_slash_option(scan_state,
+											   OT_NO_EVAL, NULL, false)))
+			free(value);
+	}
 
-	/* \encoding -- set/show client side encoding */
-	else if (strcmp(cmd, "encoding") == 0)
+	return true;
+}
+
+/* \encoding -- set/show client side encoding */
+static bool
+exec_command_encoding(PsqlScanState scan_state)
+{
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *encoding = psql_scan_slash_option(scan_state,
-													  OT_NORMAL, NULL, false);
+		char   *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!encoding)
 		{
@@ -874,9 +1121,17 @@ exec_command(const char *cmd,
 			free(encoding);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \errverbose -- display verbose message from last failed query */
-	else if (strcmp(cmd, "errverbose") == 0)
+	return true;
+}
+
+/* \errverbose -- display verbose message from last failed query */
+static bool
+exec_command_errverbose(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (pset.last_error_result)
 		{
@@ -896,25 +1151,39 @@ exec_command(const char *cmd,
 		else
 			puts(_("There is no previous error."));
 	}
+	return true;
+}
 
-	/* \f -- change field separator */
-	else if (strcmp(cmd, "f") == 0)
+/* \f -- change field separator */
+static bool
+exec_command_f(PsqlScanState scan_state)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		char	   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * \g [filename] -- send query, optionally with output to file/pipe
-	 * \gx [filename] -- same as \g, with expanded mode forced
-	 */
-	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+	return success;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static bool
+exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, false);
+		char	*fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false);
 
 		if (!fname)
 			pset.gfname = NULL;
@@ -926,21 +1195,33 @@ exec_command(const char *cmd,
 		free(fname);
 		if (strcmp(cmd, "gx") == 0)
 			pset.g_expanded = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \gexec -- send query and execute each field of result */
-	else if (strcmp(cmd, "gexec") == 0)
+	return true;
+}
+
+/* \gexec -- send query and execute each field of result */
+static bool
+exec_command_gexec(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
 		pset.gexec_flag = true;
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	return true;
+}
 
-	/* \gset [prefix] -- send query and store result into variables */
-	else if (strcmp(cmd, "gset") == 0)
+/* \gset [prefix] -- send query and store result into variables */
+static bool
+exec_command_gset(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
 	{
-		char	   *prefix = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		char	   *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (prefix)
 			pset.gset_prefix = prefix;
@@ -950,15 +1231,23 @@ exec_command(const char *cmd,
 			pset.gset_prefix = pg_strdup("");
 		}
 		/* gset_prefix is freed later */
-		status = PSQL_CMD_SEND;
+		*status = PSQL_CMD_SEND;
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* help */
-	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
-	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
-		size_t		len;
+	return true;
+}
+
+/* help */
+static bool
+exec_command_help(PsqlScanState scan_state)
+{
+
+	if (is_active_branch(scan_state))
+	{
+		size_t		len;
+		char	   *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		/* strip any trailing spaces and semicolons */
 		if (opt)
@@ -973,9 +1262,19 @@ exec_command(const char *cmd,
 		helpSQL(opt, pset.popt.topt.pager);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* HTML mode */
-	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+	return true;
+}
+
+/* HTML mode */
+static bool
+exec_command_html(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		if (pset.popt.topt.format != PRINT_HTML)
 			success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -983,13 +1282,19 @@ exec_command(const char *cmd,
 			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
 	}
 
+	return success;
+}
+
 
-	/* \i and \ir include files */
-	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+/* \i and \ir include files */
+static bool
+exec_command_include(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (!fname)
 		{
@@ -1007,16 +1312,185 @@ exec_command(const char *cmd,
 			free(fname);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \l is list databases */
-	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+	return success;
+}
+
+/*
+ * \if <expr> is the beginning of an \if..\endif block.  <expr> is parsed as
+ * a boolean expression. Invalid expressions will emit a warning and be
+ * treated as false.  Statements that follow a false expression will be parsed
+ * but ignored.  Note that in the case where an \if statment is itself within
+ * a false/ignored section of a block, then the entire \if..\endif block will
+ * be parsed but ignored.
+ */
+static bool
+exec_command_if(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+
+	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;
+	}
+
+	return true;
+}
+
+/*
+ * \elif <expr> is part of an \if..\endif block. <expr> is evaluated
+ * same as \if <expr>.
+ */
+static bool
+exec_command_elif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	switch (conditional_stack_peek(cstack))
+	{
+		case IFSTATE_IGNORED:
+			/*
+			 * inactive branch, digest expression and move on.
+			 * either if-endif already had a true section,
+			 * or whole 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 section 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;
+	}
+
+	return success;
+}
+
+/*
+ * \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.
+ */
+static bool
+exec_command_else(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	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;
+	}
+
+	return success;
+}
+
+/*
+ * \endif - closing statment of an \if...\endif block
+ */
+static bool
+exec_command_endif(PsqlScanState scan_state)
+{
+	ConditionalStack cstack = get_conditional_stack(scan_state);
+	bool	success = true;
+
+	/*
+	 * 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;
+	}
+
+	return success;
+}
+
+
+/* \l is list databases */
+static bool
+exec_command_list(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *pattern;
 		bool		show_verbose;
 
-		pattern = psql_scan_slash_option(scan_state,
-										 OT_NORMAL, NULL, true);
+		pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -1025,19 +1499,25 @@ exec_command(const char *cmd,
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/*
-	 * large object things
-	 */
-	else if (strncmp(cmd, "lo_", 3) == 0)
-	{
-		char	   *opt1,
-				   *opt2;
 
-		opt1 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
-		opt2 = psql_scan_slash_option(scan_state,
-									  OT_NORMAL, NULL, true);
+	return success;
+}
+
+/*
+ * large object things
+ */
+static bool
+exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
+	{
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+		char *opt2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		if (strcmp(cmd + 3, "export") == 0)
 		{
@@ -1082,26 +1562,43 @@ exec_command(const char *cmd,
 		}
 
 		else
-			status = PSQL_CMD_UNKNOWN;
+			*status = PSQL_CMD_UNKNOWN;
 
 		free(opt1);
 		free(opt2);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
+	return success;
+}
 
-	/* \o -- set query output */
-	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+
+/* \o -- set query output */
+static bool
+exec_command_out(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_FILEPIPE, NULL, true);
+		char   *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true);
 
 		expand_tilde(&fname);
 		success = setQFout(fname);
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \p prints the current query buffer */
-	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+	return success;
+}
+
+/* \p prints the current query buffer */
+static bool
+exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		if (query_buf && query_buf->len > 0)
 			puts(query_buf->data);
@@ -1110,8 +1607,17 @@ exec_command(const char *cmd,
 		fflush(stdout);
 	}
 
-	/* \password -- set user password */
-	else if (strcmp(cmd, "password") == 0)
+	return true;
+}
+
+
+/* \password -- set user password */
+static bool
+exec_command_password(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char		pw1[100];
 		char		pw2[100];
@@ -1164,17 +1670,24 @@ exec_command(const char *cmd,
 				free(opt0);
 		}
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \prompt -- prompt and set variable */
-	else if (strcmp(cmd, "prompt") == 0)
+	return success;
+}
+
+/* \prompt -- prompt and set variable */
+static bool
+exec_command_prompt(PsqlScanState scan_state, const char *cmd)
+{
+	bool		success = true;
+
+	if (is_active_branch(scan_state))
 	{
 		char	   *opt,
 				   *prompt_text = NULL;
-		char	   *arg1,
-				   *arg2;
-
-		arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
-		arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char	   *arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!arg1)
 		{
@@ -1225,14 +1738,22 @@ exec_command(const char *cmd,
 			free(opt);
 		}
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \pset -- set printing parameters */
-	else if (strcmp(cmd, "pset") == 0)
+	return success;
+}
+
+/* \pset -- set printing parameters */
+static bool
+exec_command_pset(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
-		char	   *opt1 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1267,13 +1788,27 @@ exec_command(const char *cmd,
 		free(opt0);
 		free(opt1);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \q or \quit */
-	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
-		status = PSQL_CMD_TERMINATE;
+	return success;
+}
 
-	/* reset(clear) the buffer */
-	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+/* \q or \quit */
+static bool
+exec_command_quit(PsqlScanState scan_state, backslashResult *status)
+{
+	if (is_active_branch(scan_state))
+		*status = PSQL_CMD_TERMINATE;
+
+	return true;
+}
+
+/* reset(clear) the buffer */
+static bool
+exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	if (is_active_branch(scan_state))
 	{
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
@@ -1281,11 +1816,18 @@ exec_command(const char *cmd,
 			puts(_("Query buffer reset (cleared)."));
 	}
 
-	/* \s save history in a file or show it on the screen */
-	else if (strcmp(cmd, "s") == 0)
+	return true;
+}
+
+/* \s save history in a file or show it on the screen */
+static bool
+exec_command_s(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *fname = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, true);
+		char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		expand_tilde(&fname);
 		success = printHistory(fname, pset.popt.topt.pager);
@@ -1295,12 +1837,21 @@ exec_command(const char *cmd,
 			putchar('\n');
 		free(fname);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \set -- generalized set variable/option command */
-	else if (strcmp(cmd, "set") == 0)
+	return success;
+}
+
+/* \set -- generalized set variable/option command */
+static bool
+exec_command_set(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0)
 		{
@@ -1336,15 +1887,23 @@ exec_command(const char *cmd,
 		}
 		free(opt0);
 	}
+	else
+		while (ignore_slash_option(scan_state))
+			continue;
 
+	return success;
+}
 
-	/* \setenv -- set environment command */
-	else if (strcmp(cmd, "setenv") == 0)
+/* \setenv -- set environment command */
+static bool
+exec_command_setenv(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *envvar = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
-		char	   *envval = psql_scan_slash_option(scan_state,
-													OT_NORMAL, NULL, false);
+		char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		char *envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!envvar)
 		{
@@ -1381,14 +1940,24 @@ exec_command(const char *cmd,
 		free(envvar);
 		free(envval);
 	}
+	else
+		ignore_2_slash_options(scan_state);
 
-	/* \sf -- show a function's source code */
-	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	return success;
+}
+
+/* \sf -- show a function's source code */
+static bool
+exec_command_sf(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
 		PQExpBuffer func_buf;
-		char	   *func;
 		Oid			foid = InvalidOid;
+		char	   *func;
 
 		func_buf = createPQExpBuffer();
 		func = psql_scan_slash_option(scan_state,
@@ -1400,22 +1969,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing function source.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!func)
 		{
 			psql_error("function name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableFunction, func, &foid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableFunction, foid, func_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1463,14 +2032,24 @@ exec_command(const char *cmd,
 			free(func);
 		destroyPQExpBuffer(func_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \sv -- show a view's source code */
-	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	return true;
+}
+
+/* \sv -- show a view's source code */
+static bool
+exec_command_sv(PsqlScanState scan_state, const char *cmd,
+				backslashResult *status)
+{
+
+	if (is_active_branch(scan_state))
 	{
 		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
 		PQExpBuffer view_buf;
-		char	   *view;
 		Oid			view_oid = InvalidOid;
+		char	   *view;
 
 		view_buf = createPQExpBuffer();
 		view = psql_scan_slash_option(scan_state,
@@ -1482,22 +2061,22 @@ exec_command(const char *cmd,
 			psql_error("The server (version %s) does not support showing view definitions.\n",
 					   formatPGVersionNumber(pset.sversion, false,
 											 sverbuf, sizeof(sverbuf)));
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!view)
 		{
 			psql_error("view name is required\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!lookup_object_oid(EditableView, view, &view_oid))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else if (!get_create_object_cmd(EditableView, view_oid, view_buf))
 		{
 			/* error already reported */
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1539,32 +2118,59 @@ exec_command(const char *cmd,
 			free(view);
 		destroyPQExpBuffer(view_buf);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \t -- turn off headers and row count */
-	else if (strcmp(cmd, "t") == 0)
+	return true;
+}
+
+/* \t -- turn off headers and row count */
+static bool
+exec_command_t(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \T -- define html <table ...> attributes */
-	else if (strcmp(cmd, "T") == 0)
+	return success;
+}
+
+/* \T -- define html <table ...> attributes */
+static bool
+exec_command_T(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *value = psql_scan_slash_option(scan_state,
-												   OT_NORMAL, NULL, false);
+		char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
 		free(value);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \timing -- toggle timing of queries */
-	else if (strcmp(cmd, "timing") == 0)
+	return success;
+}
+
+/* \timing -- toggle timing of queries */
+static bool
+exec_command_timing(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (opt)
 			success = ParseVariableBool(opt, "\\timing", &pset.timing);
@@ -1579,12 +2185,21 @@ exec_command(const char *cmd,
 		}
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \unset */
-	else if (strcmp(cmd, "unset") == 0)
+	return success;
+}
+
+/* \unset */
+static bool
+exec_command_unset(PsqlScanState scan_state, const char *cmd)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt)
 		{
@@ -1596,18 +2211,31 @@ exec_command(const char *cmd,
 
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \w -- write query buffer to file */
-	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+	return success;
+}
+
+
+
+/* \w -- write query buffer to file */
+static bool
+exec_command_write(PsqlScanState scan_state, const char *cmd,
+					PQExpBuffer query_buf, backslashResult *status)
+{
+	bool	success = true;
+	char   *fname = NULL;
+
+	if (is_active_branch(scan_state))
 	{
 		FILE	   *fd = NULL;
 		bool		is_pipe = false;
-		char	   *fname = NULL;
 
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
-			status = PSQL_CMD_ERROR;
+			*status = PSQL_CMD_ERROR;
 		}
 		else
 		{
@@ -1665,14 +2293,32 @@ exec_command(const char *cmd,
 
 		free(fname);
 	}
+	else
+	{
+		fname = psql_scan_slash_option(scan_state,
+									   OT_FILEPIPE, NULL, true);
+		if (fname)
+			free(fname);
+	}
 
-	/* \watch -- execute a query every N seconds */
-	else if (strcmp(cmd, "watch") == 0)
+	return success;
+}
+
+
+
+
+/* \watch -- execute a query every N seconds */
+static bool
+exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
 		double		sleep = 2;
 
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
 		/* Convert optional sleep-length argument */
 		if (opt)
 		{
@@ -1688,43 +2334,77 @@ exec_command(const char *cmd,
 		resetPQExpBuffer(query_buf);
 		psql_scan_reset(scan_state);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \x -- set or toggle expanded table representation */
-	else if (strcmp(cmd, "x") == 0)
+	return success;
+}
+
+/* \x -- set or toggle expanded table representation */
+static bool
+exec_command_x(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_NORMAL, NULL, true);
+		char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
 		free(opt);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \z -- list table rights (equivalent to \dp) */
-	else if (strcmp(cmd, "z") == 0)
+	return success;
+}
+
+/* \z -- list table rights (equivalent to \dp) */
+static bool
+exec_command_z(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *pattern = psql_scan_slash_option(scan_state,
-													 OT_NORMAL, NULL, true);
+		char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
 
 		success = permissionsList(pattern);
 		if (pattern)
 			free(pattern);
 	}
+	else
+		ignore_slash_option(scan_state);
 
-	/* \! -- shell escape */
-	else if (strcmp(cmd, "!") == 0)
+	return success;
+}
+
+/* \! -- shell escape */
+static bool
+exec_command_shell_escape(PsqlScanState scan_state)
+{
+	bool	success = true;
+
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt = psql_scan_slash_option(scan_state,
-												 OT_WHOLE_LINE, NULL, false);
+		char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false);
 
 		success = do_shell(opt);
 		free(opt);
 	}
+	else
+		ignore_slash_line(scan_state);
 
-	/* \? -- slash command help */
-	else if (strcmp(cmd, "?") == 0)
+	return success;
+}
+
+/* \? -- slash command help */
+static bool
+exec_command_slash_command_help(PsqlScanState scan_state)
+{
+	if (is_active_branch(scan_state))
 	{
-		char	   *opt0 = psql_scan_slash_option(scan_state,
-												  OT_NORMAL, NULL, false);
+		char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
 
 		if (!opt0 || strcmp(opt0, "commands") == 0)
 			slashUsage(pset.popt.topt.pager);
@@ -1734,8 +2414,141 @@ exec_command(const char *cmd,
 			helpVariables(pset.popt.topt.pager);
 		else
 			slashUsage(pset.popt.topt.pager);
+
+		if (opt0)
+			free(opt0);
+	}
+	else
+		ignore_slash_option(scan_state);
+
+	return true;
+}
+
+
+
+/*
+ * Subroutine to actually try to execute a backslash command.
+ */
+static backslashResult
+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 (pset.cur_cmd_interactive && !conditional_active(cstack) &&
+			!is_branching_command(cmd))
+	{
+		psql_error("command ignored, use \\endif or Ctrl-C to exit "
+					"current branch.\n");
 	}
 
+	if (strcmp(cmd, "a") == 0)
+		success = exec_command_a(scan_state);
+	else if (strcmp(cmd, "C") == 0)
+		success = exec_command_C(scan_state);
+	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+		success = exec_command_connect(scan_state);
+	else if (strcmp(cmd, "cd") == 0)
+		success = exec_command_cd(scan_state, cmd);
+	else if (strcmp(cmd, "conninfo") == 0)
+		success = exec_command_conninfo(scan_state);
+	else if (pg_strcasecmp(cmd, "copy") == 0)
+		success = exec_command_copy(scan_state);
+	else if (strcmp(cmd, "copyright") == 0)
+		success = exec_command_copyright(scan_state);
+	else if (strcmp(cmd, "crosstabview") == 0)
+		success = exec_command_crosstabview(scan_state, &status);
+	else if (cmd[0] == 'd')
+		success = exec_command_d(scan_state, cmd, &status);
+	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+		success = exec_command_edit(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ef") == 0)
+		success = exec_command_ef(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "ev") == 0)
+		success = exec_command_ev(scan_state, query_buf, &status);
+	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+		success = exec_command_echo(scan_state, cmd);
+	else if (strcmp(cmd, "encoding") == 0)
+		success = exec_command_encoding(scan_state);
+	else if (strcmp(cmd, "errverbose") == 0)
+		success = exec_command_errverbose(scan_state);
+	else if (strcmp(cmd, "f") == 0)
+		success = exec_command_f(scan_state);
+	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+		success = exec_command_g(scan_state, cmd, &status);
+	else if (strcmp(cmd, "gexec") == 0)
+		success = exec_command_gexec(scan_state, &status);
+	else if (strcmp(cmd, "gset") == 0)
+		success = exec_command_gset(scan_state, &status);
+	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+		success = exec_command_help(scan_state);
+	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+		success = exec_command_html(scan_state);
+	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+		success = exec_command_include(scan_state, cmd);
+	else if (strcmp(cmd, "if") == 0)
+		success = exec_command_if(scan_state);
+	else if (strcmp(cmd, "elif") == 0)
+		success = exec_command_elif(scan_state);
+	else if (strcmp(cmd, "else") == 0)
+		success = exec_command_else(scan_state);
+	else if (strcmp(cmd, "endif") == 0)
+		success = exec_command_endif(scan_state);
+	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+		success = exec_command_list(scan_state, cmd);
+	else if (strncmp(cmd, "lo_", 3) == 0)
+		success = exec_command_lo(scan_state, cmd, &status);
+	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+		success = exec_command_out(scan_state);
+	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+		success = exec_command_print(scan_state, query_buf);
+	else if (strcmp(cmd, "password") == 0)
+		success = exec_command_password(scan_state);
+	else if (strcmp(cmd, "prompt") == 0)
+		success = exec_command_prompt(scan_state, cmd);
+	else if (strcmp(cmd, "pset") == 0)
+		success = exec_command_pset(scan_state);
+	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+		success = exec_command_quit(scan_state, &status);
+	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+		success = exec_command_reset(scan_state, query_buf);
+	else if (strcmp(cmd, "s") == 0)
+		success = exec_command_s(scan_state);
+	else if (strcmp(cmd, "set") == 0)
+		success = exec_command_set(scan_state);
+	else if (strcmp(cmd, "setenv") == 0)
+		success = exec_command_setenv(scan_state, cmd);
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+		success = exec_command_sf(scan_state, cmd, &status);
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+		success = exec_command_sv(scan_state, cmd, &status);
+	else if (strcmp(cmd, "t") == 0)
+		success = exec_command_t(scan_state);
+	else if (strcmp(cmd, "T") == 0)
+		success = exec_command_T(scan_state);
+	else if (strcmp(cmd, "timing") == 0)
+		success = exec_command_timing(scan_state);
+	else if (strcmp(cmd, "unset") == 0)
+		success = exec_command_unset(scan_state, cmd);
+	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+		success = exec_command_write(scan_state, cmd, query_buf, &status);
+	else if (strcmp(cmd, "watch") == 0)
+		success = exec_command_watch(scan_state, query_buf);
+	else if (strcmp(cmd, "x") == 0)
+		success = exec_command_x(scan_state);
+	else if (strcmp(cmd, "z") == 0)
+		success = exec_command_z(scan_state);
+	else if (strcmp(cmd, "!") == 0)
+		success = exec_command_shell_escape(scan_state);
+	else if (strcmp(cmd, "?") == 0)
+		success = exec_command_slash_command_help(scan_state);
+
 #if 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/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..8643ff1
--- /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(void)
+{
+	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+	cstack->head = NULL;
+	return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+	while (conditional_stack_pop(cstack))
+		continue;
+	free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack,	ifState new_state)
+{
+	IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem));
+	p->if_state = new_state;
+	p->next = cstack->head;
+	cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(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(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 no branch state to set.
+ */
+bool
+conditional_stack_poke(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-blocks
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+	return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block is active, or if there is no
+ * open \if (ie, we should execute commands normally)
+ */
+bool
+conditional_active(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..96dbd7f
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,62 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+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;
+
+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 void 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..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..7809629 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,6 +332,7 @@ 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);
@@ -339,11 +341,15 @@ main(int argc, char *argv[])
 				psql_scan_setup(scan_state,
 								cell->val, strlen(cell->val),
 								pset.encoding, standard_strings());
+				cond_stack = conditional_stack_create();
+				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
-				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..5774c98 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,116 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+should print #8-1
+\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..078a76f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,115 @@ 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 vars are not expanded and commands are ignored but args are parsed
+\set try_to_quit '\\q'
+\if false
+	:try_to_quit
+	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+	\copy arg1 arg2 arg3 arg4 arg5 arg6
+	\copyright \dt arg1 \e arg1 arg2
+	\ef whole_line
+	\ev whole_line
+	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+	\sf whole_line
+	\sv whole_line
+	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+	\! whole_line
+\else
+	\echo 'should print #8-1'
+\endif
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
-- 
2.7.4



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-29 20:51  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Corey Huinker <corey.huinker@gmail.com>
  0 siblings, 1 reply; 232+ messages in thread

From: Fabien COELHO @ 2017-03-29 20:51 UTC (permalink / raw)
  To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> New Patch v29: Now with less coverage!

Patch applies cleanly. Make check ok. Feature still works!

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-30 04:15  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 3 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-30 04:15 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> New Patch v29: Now with less coverage!

> Patch applies cleanly. Make check ok. Feature still works!

I've been hacking on this for about two full days now, and have gotten
it to a point where I think it's committable.  Aside from cosmetic
changes, I've made it behave reasonably for cases where \if is used
on portions of a query, for instance

SELECT
\if :something
    var1
\else
    var2
\endif
FROM table;

which as I mentioned a long time ago is something that people will
certainly expect to work.  I also cleaned up a lot of corner-case
discrepancies between how much text is consumed in active-branch and
inactive-branch cases (OT_FILEPIPE is a particularly nasty case in that
regard :-()

I plan to read this over again tomorrow and then push it, if there are
not objections/corrections.

			regards, tom lane

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-diff] psql-if-v30.patch (113.1K, ../../10990.1490847359@sss.pgh.pa.us/2-psql-if-v30.patch)
  download | inline diff:
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..b51b11b 100644
*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
*************** hello 10
*** 2064,2069 ****
--- 2064,2158 ----
  
  
        <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
+         their argument(s) and evaluate them as a boolean expression.  If the
+         expression yields <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> test has
+         succeeded, the arguments of later <command>\elif</command> commands in
+         the same block 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>
+         The <replaceable class="parameter">expression</replaceable> argument
+         of an <command>\if</command> or <command>\elif</command> 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 a warning and be treated as false.
+         </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.
+         Variable references in skipped lines are not expanded, and backquote
+         expansion is not performed either.
+         </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>
+ -- 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>
*************** testdb=&gt; <userinput>INSERT INTO my_ta
*** 3715,3721 ****
          <listitem>
          <para>
          In prompt 1 normally <literal>=</literal>,
!         but <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
--- 3804,3811 ----
          <listitem>
          <para>
          In prompt 1 normally <literal>=</literal>,
!         but <literal>@</literal> if the session is in an inactive branch of a
!         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/Makefile b/src/bin/psql/Makefile
index f8e31ea..ab2cfa6 100644
*** a/src/bin/psql/Makefile
--- b/src/bin/psql/Makefile
*************** REFDOCDIR= $(top_srcdir)/doc/src/sgml/re
*** 21,30 ****
  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 \
! 	startup.o prompt.o variables.o large_obj.o describe.o \
! 	crosstabview.o tab-complete.o \
! 	sql_help.o psqlscanslash.o \
  	$(WIN32RES)
  
  
--- 21,30 ----
  override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
  LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
  
! OBJS=	command.o common.o conditional.o copy.o crosstabview.o \
! 	describe.o help.o input.o large_obj.o mainloop.o \
! 	prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
! 	tab-complete.o variables.o \
  	$(WIN32RES)
  
  
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..e278511 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** typedef enum EditableObjectType
*** 56,69 ****
  	EditableView
  } EditableObjectType;
  
! /* functions for use in this file */
  static backslashResult exec_command(const char *cmd,
  			 PsqlScanState scan_state,
! 			 PQExpBuffer query_buf);
! static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		int lineno, bool *edited);
  static bool do_connect(enum trivalue reuse_previous_specification,
  		   char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool do_watch(PQExpBuffer query_buf, double sleep);
  static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
--- 56,158 ----
  	EditableView
  } EditableObjectType;
  
! /* local function declarations */
  static backslashResult exec_command(const char *cmd,
  			 PsqlScanState scan_state,
! 			 ConditionalStack cstack,
! 			 PQExpBuffer query_buf,
! 			 PQExpBuffer previous_buf);
! static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch,
! 				const char *cmd);
! static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
! 			   const char *cmd);
! static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
! 				  PQExpBuffer query_buf, PQExpBuffer previous_buf);
! static backslashResult exec_command_ef(PsqlScanState scan_state, bool active_branch,
! 				PQExpBuffer query_buf);
! static backslashResult exec_command_ev(PsqlScanState scan_state, bool active_branch,
! 				PQExpBuffer query_buf);
! static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch,
! 				  const char *cmd);
! static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
! 				  PQExpBuffer query_buf);
! static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
! 				  PQExpBuffer query_buf);
! static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
! 				   PQExpBuffer query_buf);
! static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
! 			   const char *cmd);
! static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch,
! 					 const char *cmd);
! static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
! 				PQExpBuffer query_buf);
! static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch,
! 				  const char *cmd);
! static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch,
! 				const char *cmd);
! static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf);
! static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
! 					const char *cmd);
! static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf);
! static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
! 					const char *cmd);
! static backslashResult exec_command_sf(PsqlScanState scan_state, bool active_branch,
! 				const char *cmd);
! static backslashResult exec_command_sv(PsqlScanState scan_state, bool active_branch,
! 				const char *cmd);
! static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
! 				   const char *cmd);
! static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
! 				   const char *cmd,
! 				   PQExpBuffer query_buf, PQExpBuffer previous_buf);
! static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf, PQExpBuffer previous_buf);
! static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch);
! static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch);
! static char *read_connect_arg(PsqlScanState scan_state);
! static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state);
! static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name);
! static void ignore_boolean_expression(PsqlScanState scan_state);
! static void ignore_slash_options(PsqlScanState scan_state);
! static void ignore_slash_filepipe(PsqlScanState scan_state);
! static void ignore_slash_whole_line(PsqlScanState scan_state);
! static bool is_branching_command(const char *cmd);
! static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
! 					  PQExpBuffer query_buf);
! static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
! 				   PQExpBuffer query_buf);
! static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf);
  static bool do_connect(enum trivalue reuse_previous_specification,
  		   char *dbname, char *user, char *host, char *port);
+ static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
+ 		int lineno, bool *edited);
  static bool do_shell(const char *command);
  static bool do_watch(PQExpBuffer query_buf, double sleep);
  static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
*************** static void checkWin32Codepage(void);
*** 96,104 ****
   * just after the '\'.  The lexer is advanced past the command and all
   * arguments on return.
   *
!  * 'query_buf' contains the query-so-far, which may be modified by
   * execution of the backslash command (for example, \r clears it).
!  * query_buf can be NULL if there is no query so far.
   *
   * Returns a status code indicating what action is desired, see command.h.
   *----------
--- 185,202 ----
   * just after the '\'.  The lexer is advanced past the command and all
   * arguments on return.
   *
!  * cstack is the current \if stack state.  This will be examined, and
!  * possibly modified by conditional commands.
!  *
!  * query_buf contains the query-so-far, which may be modified by
   * execution of the backslash command (for example, \r clears it).
!  *
!  * previous_buf contains the query most recently sent to the server
!  * (empty if none yet).  This should not be modified here, but some
!  * commands copy its content into query_buf.
!  *
!  * query_buf and previous_buf will be NULL when executing a "-c"
!  * command-line option.
   *
   * Returns a status code indicating what action is desired, see command.h.
   *----------
*************** static void checkWin32Codepage(void);
*** 106,124 ****
  
  backslashResult
  HandleSlashCmds(PsqlScanState scan_state,
! 				PQExpBuffer query_buf)
  {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
  	char	   *cmd;
  	char	   *arg;
  
  	Assert(scan_state != NULL);
  
  	/* Parse off the command name */
  	cmd = psql_scan_slash_command(scan_state);
  
  	/* And try to execute it */
! 	status = exec_command(cmd, scan_state, query_buf);
  
  	if (status == PSQL_CMD_UNKNOWN)
  	{
--- 204,225 ----
  
  backslashResult
  HandleSlashCmds(PsqlScanState scan_state,
! 				ConditionalStack cstack,
! 				PQExpBuffer query_buf,
! 				PQExpBuffer previous_buf)
  {
! 	backslashResult status;
  	char	   *cmd;
  	char	   *arg;
  
  	Assert(scan_state != NULL);
+ 	Assert(cstack != NULL);
  
  	/* Parse off the command name */
  	cmd = psql_scan_slash_command(scan_state);
  
  	/* And try to execute it */
! 	status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
  
  	if (status == PSQL_CMD_UNKNOWN)
  	{
*************** HandleSlashCmds(PsqlScanState scan_state
*** 131,144 ****
  
  	if (status != PSQL_CMD_ERROR)
  	{
! 		/* eat any remaining arguments after a valid command */
! 		/* note we suppress evaluation of backticks here */
  		while ((arg = psql_scan_slash_option(scan_state,
! 											 OT_NO_EVAL, NULL, false)))
  		{
! 			psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
  			free(arg);
  		}
  	}
  	else
  	{
--- 232,253 ----
  
  	if (status != PSQL_CMD_ERROR)
  	{
! 		/*
! 		 * Eat any remaining arguments after a valid command.  We want to
! 		 * suppress evaluation of backticks in this situation, so transiently
! 		 * push an inactive conditional-stack entry.
! 		 */
! 		bool		active_branch = conditional_active(cstack);
! 
! 		conditional_stack_push(cstack, IFSTATE_IGNORED);
  		while ((arg = psql_scan_slash_option(scan_state,
! 											 OT_NORMAL, NULL, false)))
  		{
! 			if (active_branch)
! 				psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
  			free(arg);
  		}
+ 		conditional_stack_pop(cstack);
  	}
  	else
  	{
*************** HandleSlashCmds(PsqlScanState scan_state
*** 159,214 ****
  	return status;
  }
  
  /*
!  * Read and interpret an argument to the \connect slash command.
   */
! static char *
! read_connect_arg(PsqlScanState scan_state)
  {
! 	char	   *result;
! 	char		quote;
  
  	/*
! 	 * Ideally we should treat the arguments as SQL identifiers.  But for
! 	 * backwards compatibility with 7.2 and older pg_dump files, we have to
! 	 * take unquoted arguments verbatim (don't downcase them). For now,
! 	 * double-quoted arguments may be stripped of double quotes (as if SQL
! 	 * identifiers).  By 7.4 or so, pg_dump files can be expected to
! 	 * double-quote all mixed-case \connect arguments, and then we can get rid
! 	 * of OT_SQLIDHACK.
  	 */
! 	result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
! 
! 	if (!result)
! 		return NULL;
  
! 	if (quote)
! 		return result;
  
! 	if (*result == '\0' || strcmp(result, "-") == 0)
! 		return NULL;
  
! 	return result;
  }
  
  
  /*
!  * Subroutine to actually try to execute a backslash command.
   */
  static backslashResult
! exec_command(const char *cmd,
! 			 PsqlScanState scan_state,
! 			 PQExpBuffer query_buf)
  {
! 	bool		success = true; /* indicate here if the command ran ok or
! 								 * failed */
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
  
! 	/*
! 	 * \a -- toggle field alignment This makes little sense but we keep it
! 	 * around.
! 	 */
! 	if (strcmp(cmd, "a") == 0)
  	{
  		if (pset.popt.topt.format != PRINT_ALIGNED)
  			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
--- 268,436 ----
  	return status;
  }
  
+ 
  /*
!  * Subroutine to actually try to execute a backslash command.
!  *
!  * The typical "success" result code is PSQL_CMD_SKIP_LINE, although some
!  * commands return something else.  Failure results are PSQL_CMD_ERROR,
!  * unless PSQL_CMD_UNKNOWN is more appropriate.
   */
! static backslashResult
! exec_command(const char *cmd,
! 			 PsqlScanState scan_state,
! 			 ConditionalStack cstack,
! 			 PQExpBuffer query_buf,
! 			 PQExpBuffer previous_buf)
  {
! 	backslashResult status;
! 	bool		active_branch = conditional_active(cstack);
  
  	/*
! 	 * In interactive mode, warn when we're ignoring a command within a false
! 	 * \if-branch.  But we continue on, so as to parse and discard the right
! 	 * number of parameters.  Each individual backslash command subroutine is
! 	 * responsible for doing nothing after discarding appropriate arguments,
! 	 * if !active_branch.
  	 */
! 	if (pset.cur_cmd_interactive && !active_branch &&
! 		!is_branching_command(cmd))
! 	{
! 		psql_error("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block\n",
! 				   cmd);
! 	}
  
! 	if (strcmp(cmd, "a") == 0)
! 		status = exec_command_a(scan_state, active_branch);
! 	else if (strcmp(cmd, "C") == 0)
! 		status = exec_command_C(scan_state, active_branch);
! 	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
! 		status = exec_command_connect(scan_state, active_branch);
! 	else if (strcmp(cmd, "cd") == 0)
! 		status = exec_command_cd(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "conninfo") == 0)
! 		status = exec_command_conninfo(scan_state, active_branch);
! 	else if (pg_strcasecmp(cmd, "copy") == 0)
! 		status = exec_command_copy(scan_state, active_branch);
! 	else if (strcmp(cmd, "copyright") == 0)
! 		status = exec_command_copyright(scan_state, active_branch);
! 	else if (strcmp(cmd, "crosstabview") == 0)
! 		status = exec_command_crosstabview(scan_state, active_branch);
! 	else if (cmd[0] == 'd')
! 		status = exec_command_d(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
! 		status = exec_command_edit(scan_state, active_branch,
! 								   query_buf, previous_buf);
! 	else if (strcmp(cmd, "ef") == 0)
! 		status = exec_command_ef(scan_state, active_branch, query_buf);
! 	else if (strcmp(cmd, "ev") == 0)
! 		status = exec_command_ev(scan_state, active_branch, query_buf);
! 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
! 		status = exec_command_echo(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "elif") == 0)
! 		status = exec_command_elif(scan_state, cstack, query_buf);
! 	else if (strcmp(cmd, "else") == 0)
! 		status = exec_command_else(scan_state, cstack, query_buf);
! 	else if (strcmp(cmd, "endif") == 0)
! 		status = exec_command_endif(scan_state, cstack, query_buf);
! 	else if (strcmp(cmd, "encoding") == 0)
! 		status = exec_command_encoding(scan_state, active_branch);
! 	else if (strcmp(cmd, "errverbose") == 0)
! 		status = exec_command_errverbose(scan_state, active_branch);
! 	else if (strcmp(cmd, "f") == 0)
! 		status = exec_command_f(scan_state, active_branch);
! 	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
! 		status = exec_command_g(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "gexec") == 0)
! 		status = exec_command_gexec(scan_state, active_branch);
! 	else if (strcmp(cmd, "gset") == 0)
! 		status = exec_command_gset(scan_state, active_branch);
! 	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
! 		status = exec_command_help(scan_state, active_branch);
! 	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
! 		status = exec_command_html(scan_state, active_branch);
! 	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 ||
! 			 strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
! 		status = exec_command_include(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "if") == 0)
! 		status = exec_command_if(scan_state, cstack, query_buf);
! 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
! 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
! 		status = exec_command_list(scan_state, active_branch, cmd);
! 	else if (strncmp(cmd, "lo_", 3) == 0)
! 		status = exec_command_lo(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
! 		status = exec_command_out(scan_state, active_branch);
! 	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
! 		status = exec_command_print(scan_state, active_branch, query_buf);
! 	else if (strcmp(cmd, "password") == 0)
! 		status = exec_command_password(scan_state, active_branch);
! 	else if (strcmp(cmd, "prompt") == 0)
! 		status = exec_command_prompt(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "pset") == 0)
! 		status = exec_command_pset(scan_state, active_branch);
! 	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
! 		status = exec_command_quit(scan_state, active_branch);
! 	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
! 		status = exec_command_reset(scan_state, active_branch, query_buf);
! 	else if (strcmp(cmd, "s") == 0)
! 		status = exec_command_s(scan_state, active_branch);
! 	else if (strcmp(cmd, "set") == 0)
! 		status = exec_command_set(scan_state, active_branch);
! 	else if (strcmp(cmd, "setenv") == 0)
! 		status = exec_command_setenv(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
! 		status = exec_command_sf(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
! 		status = exec_command_sv(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "t") == 0)
! 		status = exec_command_t(scan_state, active_branch);
! 	else if (strcmp(cmd, "T") == 0)
! 		status = exec_command_T(scan_state, active_branch);
! 	else if (strcmp(cmd, "timing") == 0)
! 		status = exec_command_timing(scan_state, active_branch);
! 	else if (strcmp(cmd, "unset") == 0)
! 		status = exec_command_unset(scan_state, active_branch, cmd);
! 	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
! 		status = exec_command_write(scan_state, active_branch, cmd,
! 									query_buf, previous_buf);
! 	else if (strcmp(cmd, "watch") == 0)
! 		status = exec_command_watch(scan_state, active_branch,
! 									query_buf, previous_buf);
! 	else if (strcmp(cmd, "x") == 0)
! 		status = exec_command_x(scan_state, active_branch);
! 	else if (strcmp(cmd, "z") == 0)
! 		status = exec_command_z(scan_state, active_branch);
! 	else if (strcmp(cmd, "!") == 0)
! 		status = exec_command_shell_escape(scan_state, active_branch);
! 	else if (strcmp(cmd, "?") == 0)
! 		status = exec_command_slash_command_help(scan_state, active_branch);
! 	else
! 		status = PSQL_CMD_UNKNOWN;
  
! 	/*
! 	 * All the commands that return PSQL_CMD_SEND want to execute previous_buf
! 	 * if query_buf is empty.  For convenience we implement that here, not in
! 	 * the individual command subroutines.
! 	 */
! 	if (status == PSQL_CMD_SEND)
! 		copy_previous_query(query_buf, previous_buf);
  
! 	return status;
  }
  
  
  /*
!  * \a -- toggle field alignment
!  *
!  * This makes little sense but we keep it around.
   */
  static backslashResult
! exec_command_a(PsqlScanState scan_state, bool active_branch)
  {
! 	bool		success = true;
  
! 	if (active_branch)
  	{
  		if (pset.popt.topt.format != PRINT_ALIGNED)
  			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
*************** exec_command(const char *cmd,
*** 216,223 ****
  			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
  	}
  
! 	/* \C -- override table title (formerly change HTML caption) */
! 	else if (strcmp(cmd, "C") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
--- 438,455 ----
  			success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
  	}
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \C -- override table title (formerly change HTML caption)
!  */
! static backslashResult
! exec_command_C(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 225,244 ****
  		success = do_pset("title", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
  
! 	/*
! 	 * \c or \connect -- connect to database using the specified parameters.
! 	 *
! 	 * \c [-reuse-previous=BOOL] dbname user host port
! 	 *
! 	 * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
! 	 *
! 	 * \c - - hst		Connect to current database on current port of host
! 	 * "hst" as current user. \c - usr - prt   Connect to current database on
! 	 * "prt" port of current host as user "usr". \c dbs			  Connect to
! 	 * "dbs" database on current port of current host as current user.
! 	 */
! 	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
  	{
  		static const char prefix[] = "-reuse-previous=";
  		char	   *opt1,
--- 457,488 ----
  		success = do_pset("title", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \c or \connect -- connect to database using the specified parameters.
!  *
!  * \c [-reuse-previous=BOOL] dbname user host port
!  *
!  * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
!  *
!  * \c - - hst		Connect to current database on current port of
!  *					host "hst" as current user.
!  * \c - usr - prt	Connect to current database on port "prt" of current host
!  *					as user "usr".
!  * \c dbs			Connect to database "dbs" on current port of current host
!  *					as current user.
!  */
! static backslashResult
! exec_command_connect(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		static const char prefix[] = "-reuse-previous=";
  		char	   *opt1,
*************** exec_command(const char *cmd,
*** 277,285 ****
  		}
  		free(opt1);
  	}
  
! 	/* \cd */
! 	else if (strcmp(cmd, "cd") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
--- 521,541 ----
  		}
  		free(opt1);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \cd -- change directory
!  */
! static backslashResult
! exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 323,331 ****
  		if (opt)
  			free(opt);
  	}
  
! 	/* \conninfo -- display information about the current connection */
! 	else if (strcmp(cmd, "conninfo") == 0)
  	{
  		char	   *db = PQdb(pset.db);
  
--- 579,597 ----
  		if (opt)
  			free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \conninfo -- display information about the current connection
!  */
! static backslashResult
! exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  	{
  		char	   *db = PQdb(pset.db);
  
*************** exec_command(const char *cmd,
*** 366,373 ****
  		}
  	}
  
! 	/* \copy */
! 	else if (pg_strcasecmp(cmd, "copy") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
--- 632,649 ----
  		}
  	}
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \copy -- run a COPY command
!  */
! static backslashResult
! exec_command_copy(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
*************** exec_command(const char *cmd,
*** 375,387 ****
  		success = do_copy(opt);
  		free(opt);
  	}
  
! 	/* \copyright */
! 	else if (strcmp(cmd, "copyright") == 0)
  		print_copyright();
  
! 	/* \crosstabview -- execute a query and display results in crosstab */
! 	else if (strcmp(cmd, "crosstabview") == 0)
  	{
  		int			i;
  
--- 651,683 ----
  		success = do_copy(opt);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \copyright
!  */
! static backslashResult
! exec_command_copyright(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  		print_copyright();
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \crosstabview -- execute a query and display results in crosstab
!  */
! static backslashResult
! exec_command_crosstabview(PsqlScanState scan_state, bool active_branch)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		int			i;
  
*************** exec_command(const char *cmd,
*** 391,399 ****
  		pset.crosstab_flag = true;
  		status = PSQL_CMD_SEND;
  	}
  
! 	/* \d* commands */
! 	else if (cmd[0] == 'd')
  	{
  		char	   *pattern;
  		bool		show_verbose,
--- 687,708 ----
  		pset.crosstab_flag = true;
  		status = PSQL_CMD_SEND;
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \d* commands
!  */
! static backslashResult
! exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *pattern;
  		bool		show_verbose,
*************** exec_command(const char *cmd,
*** 502,508 ****
  					success = listDbRoleSettings(pattern, pattern2);
  				}
  				else
! 					success = PSQL_CMD_UNKNOWN;
  				break;
  			case 'R':
  				switch (cmd[2])
--- 811,817 ----
  					success = listDbRoleSettings(pattern, pattern2);
  				}
  				else
! 					status = PSQL_CMD_UNKNOWN;
  				break;
  			case 'R':
  				switch (cmd[2])
*************** exec_command(const char *cmd,
*** 580,592 ****
  		if (pattern)
  			free(pattern);
  	}
  
  
! 	/*
! 	 * \e or \edit -- edit the current query buffer, or edit a file and make
! 	 * it the query buffer
! 	 */
! 	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
  	{
  		if (!query_buf)
  		{
--- 889,914 ----
  		if (pattern)
  			free(pattern);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
+ 	if (!success)
+ 		status = PSQL_CMD_ERROR;
  
! 	return status;
! }
! 
! /*
!  * \e or \edit -- edit the current query buffer, or edit a file and
!  * make it the query buffer
!  */
! static backslashResult
! exec_command_edit(PsqlScanState scan_state, bool active_branch,
! 				  PQExpBuffer query_buf, PQExpBuffer previous_buf)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		if (!query_buf)
  		{
*************** exec_command(const char *cmd,
*** 632,637 ****
--- 954,963 ----
  				expand_tilde(&fname);
  				if (fname)
  					canonicalize_path(fname);
+ 
+ 				/* Applies to previous query if current buffer is empty */
+ 				copy_previous_query(query_buf, previous_buf);
+ 
  				if (do_edit(fname, query_buf, lineno, NULL))
  					status = PSQL_CMD_NEWEDIT;
  				else
*************** exec_command(const char *cmd,
*** 643,655 ****
  				free(ln);
  		}
  	}
  
! 	/*
! 	 * \ef -- edit the named function, or present a blank CREATE FUNCTION
! 	 * template if no argument is given
! 	 */
! 	else if (strcmp(cmd, "ef") == 0)
  	{
  		int			lineno = -1;
  
  		if (pset.sversion < 80400)
--- 969,994 ----
  				free(ln);
  		}
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \ef -- edit the named function, or present a blank CREATE FUNCTION
!  * template if no argument is given
!  */
! static backslashResult
! exec_command_ef(PsqlScanState scan_state, bool active_branch,
! 				PQExpBuffer query_buf)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
+ 		char	   *func = psql_scan_slash_option(scan_state,
+ 												  OT_WHOLE_LINE, NULL, true);
  		int			lineno = -1;
  
  		if (pset.sversion < 80400)
*************** exec_command(const char *cmd,
*** 668,678 ****
  		}
  		else
  		{
- 			char	   *func;
  			Oid			foid = InvalidOid;
  
- 			func = psql_scan_slash_option(scan_state,
- 										  OT_WHOLE_LINE, NULL, true);
  			lineno = strip_lineno_from_objdesc(func);
  			if (lineno == 0)
  			{
--- 1007,1014 ----
*************** exec_command(const char *cmd,
*** 725,733 ****
  					lines++;
  				}
  			}
- 
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
--- 1061,1066 ----
*************** exec_command(const char *cmd,
*** 741,754 ****
  			else
  				status = PSQL_CMD_NEWEDIT;
  		}
  	}
  
! 	/*
! 	 * \ev -- edit the named view, or present a blank CREATE VIEW template if
! 	 * no argument is given
! 	 */
! 	else if (strcmp(cmd, "ev") == 0)
  	{
  		int			lineno = -1;
  
  		if (pset.sversion < 70400)
--- 1074,1103 ----
  			else
  				status = PSQL_CMD_NEWEDIT;
  		}
+ 
+ 		if (func)
+ 			free(func);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \ev -- edit the named view, or present a blank CREATE VIEW
!  * template if no argument is given
!  */
! static backslashResult
! exec_command_ev(PsqlScanState scan_state, bool active_branch,
! 				PQExpBuffer query_buf)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
+ 		char	   *view = psql_scan_slash_option(scan_state,
+ 												  OT_WHOLE_LINE, NULL, true);
  		int			lineno = -1;
  
  		if (pset.sversion < 70400)
*************** exec_command(const char *cmd,
*** 767,777 ****
  		}
  		else
  		{
- 			char	   *view;
  			Oid			view_oid = InvalidOid;
  
- 			view = psql_scan_slash_option(scan_state,
- 										  OT_WHOLE_LINE, NULL, true);
  			lineno = strip_lineno_from_objdesc(view);
  			if (lineno == 0)
  			{
--- 1116,1123 ----
*************** exec_command(const char *cmd,
*** 796,804 ****
  				/* error already reported */
  				status = PSQL_CMD_ERROR;
  			}
- 
- 			if (view)
- 				free(view);
  		}
  
  		if (status != PSQL_CMD_ERROR)
--- 1142,1147 ----
*************** exec_command(const char *cmd,
*** 812,821 ****
  			else
  				status = PSQL_CMD_NEWEDIT;
  		}
  	}
  
! 	/* \echo and \qecho */
! 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
  	{
  		char	   *value;
  		char		quoted;
--- 1155,1177 ----
  			else
  				status = PSQL_CMD_NEWEDIT;
  		}
+ 
+ 		if (view)
+ 			free(view);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \echo and \qecho -- echo arguments to stdout or query output
!  */
! static backslashResult
! exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	if (active_branch)
  	{
  		char	   *value;
  		char		quoted;
*************** exec_command(const char *cmd,
*** 846,854 ****
  		if (!no_newline)
  			fputs("\n", fout);
  	}
  
! 	/* \encoding -- set/show client side encoding */
! 	else if (strcmp(cmd, "encoding") == 0)
  	{
  		char	   *encoding = psql_scan_slash_option(scan_state,
  													  OT_NORMAL, NULL, false);
--- 1202,1220 ----
  		if (!no_newline)
  			fputs("\n", fout);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \encoding -- set/show client side encoding
!  */
! static backslashResult
! exec_command_encoding(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  	{
  		char	   *encoding = psql_scan_slash_option(scan_state,
  													  OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 874,882 ****
  			free(encoding);
  		}
  	}
  
! 	/* \errverbose -- display verbose message from last failed query */
! 	else if (strcmp(cmd, "errverbose") == 0)
  	{
  		if (pset.last_error_result)
  		{
--- 1240,1258 ----
  			free(encoding);
  		}
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \errverbose -- display verbose message from last failed query
!  */
! static backslashResult
! exec_command_errverbose(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  	{
  		if (pset.last_error_result)
  		{
*************** exec_command(const char *cmd,
*** 897,904 ****
  			puts(_("There is no previous error."));
  	}
  
! 	/* \f -- change field separator */
! 	else if (strcmp(cmd, "f") == 0)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, false);
--- 1273,1290 ----
  			puts(_("There is no previous error."));
  	}
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \f -- change field separator
!  */
! static backslashResult
! exec_command_f(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 906,917 ****
  		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
  		free(fname);
  	}
  
! 	/*
! 	 * \g [filename] -- send query, optionally with output to file/pipe
! 	 * \gx [filename] -- same as \g, with expanded mode forced
! 	 */
! 	else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_FILEPIPE, NULL, false);
--- 1292,1313 ----
  		success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
  		free(fname);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \g [filename] -- send query, optionally with output to file/pipe
!  * \gx [filename] -- same as \g, with expanded mode forced
!  */
! static backslashResult
! exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_FILEPIPE, NULL, false);
*************** exec_command(const char *cmd,
*** 928,943 ****
  			pset.g_expanded = true;
  		status = PSQL_CMD_SEND;
  	}
  
! 	/* \gexec -- send query and execute each field of result */
! 	else if (strcmp(cmd, "gexec") == 0)
  	{
  		pset.gexec_flag = true;
  		status = PSQL_CMD_SEND;
  	}
  
! 	/* \gset [prefix] -- send query and store result into variables */
! 	else if (strcmp(cmd, "gset") == 0)
  	{
  		char	   *prefix = psql_scan_slash_option(scan_state,
  													OT_NORMAL, NULL, false);
--- 1324,1361 ----
  			pset.g_expanded = true;
  		status = PSQL_CMD_SEND;
  	}
+ 	else
+ 		ignore_slash_filepipe(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \gexec -- send query and execute each field of result
!  */
! static backslashResult
! exec_command_gexec(PsqlScanState scan_state, bool active_branch)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		pset.gexec_flag = true;
  		status = PSQL_CMD_SEND;
  	}
  
! 	return status;
! }
! 
! /*
!  * \gset [prefix] -- send query and store result into variables
!  */
! static backslashResult
! exec_command_gset(PsqlScanState scan_state, bool active_branch)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		char	   *prefix = psql_scan_slash_option(scan_state,
  													OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 952,960 ****
  		/* gset_prefix is freed later */
  		status = PSQL_CMD_SEND;
  	}
  
! 	/* help */
! 	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
--- 1370,1388 ----
  		/* gset_prefix is freed later */
  		status = PSQL_CMD_SEND;
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \help [topic] -- print help about SQL commands
!  */
! static backslashResult
! exec_command_help(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
*************** exec_command(const char *cmd,
*** 973,981 ****
  		helpSQL(opt, pset.popt.topt.pager);
  		free(opt);
  	}
  
! 	/* HTML mode */
! 	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
  	{
  		if (pset.popt.topt.format != PRINT_HTML)
  			success = do_pset("format", "html", &pset.popt, pset.quiet);
--- 1401,1421 ----
  		helpSQL(opt, pset.popt.topt.pager);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \H and \html -- toggle HTML formatting
!  */
! static backslashResult
! exec_command_html(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		if (pset.popt.topt.format != PRINT_HTML)
  			success = do_pset("format", "html", &pset.popt, pset.quiet);
*************** exec_command(const char *cmd,
*** 983,992 ****
  			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
  	}
  
  
! 	/* \i and \ir include files */
! 	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
! 		   || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
--- 1423,1440 ----
  			success = do_pset("format", "aligned", &pset.popt, pset.quiet);
  	}
  
+ 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+ }
  
! /*
!  * \i and \ir -- include a file
!  */
! static backslashResult
! exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1007,1016 ****
  			free(fname);
  		}
  	}
  
! 	/* \l is list databases */
! 	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
! 			 strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
  	{
  		char	   *pattern;
  		bool		show_verbose;
--- 1455,1708 ----
  			free(fname);
  		}
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \if <expr> -- beginning of an \if..\endif block
!  *
!  * <expr> is parsed as a boolean expression.  Invalid expressions will emit a
!  * warning and be treated as false.  Statements that follow a false expression
!  * will be parsed but ignored.  Note that in the case where an \if statement
!  * is itself within an inactive section of a block, then the entire inner
!  * \if..\endif block will be parsed but ignored.
!  */
! static backslashResult
! exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
! 				PQExpBuffer query_buf)
! {
! 	if (conditional_active(cstack))
! 	{
! 		/*
! 		 * First, push a new active stack entry; this ensures that the lexer
! 		 * will perform variable substitution and backtick evaluation while
! 		 * scanning the expression.  (That should happen anyway, since we know
! 		 * we're in an active outer branch, but let's be sure.)
! 		 */
! 		conditional_stack_push(cstack, IFSTATE_TRUE);
! 
! 		/* Remember current query state in case we need to restore later */
! 		save_query_text_state(scan_state, cstack, query_buf);
! 
! 		/*
! 		 * Evaluate the expression; if it's false, change to inactive state.
! 		 */
! 		if (!is_true_boolean_expression(scan_state, "\\if expression"))
! 			conditional_stack_poke(cstack, IFSTATE_FALSE);
! 	}
! 	else
! 	{
! 		/*
! 		 * We're within an inactive outer branch, so this entire \if block
! 		 * will be ignored.  We don't want to evaluate the expression, so push
! 		 * the "ignored" stack state before scanning it.
! 		 */
! 		conditional_stack_push(cstack, IFSTATE_IGNORED);
! 
! 		/* Remember current query state in case we need to restore later */
! 		save_query_text_state(scan_state, cstack, query_buf);
! 
! 		ignore_boolean_expression(scan_state);
! 	}
! 
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \elif <expr> -- alternative branch in an \if..\endif block
!  *
!  * <expr> is evaluated the same as in \if <expr>.
!  */
! static backslashResult
! exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
! 				  PQExpBuffer query_buf)
! {
! 	bool		success = true;
! 
! 	switch (conditional_stack_peek(cstack))
! 	{
! 		case IFSTATE_TRUE:
! 
! 			/*
! 			 * Just finished active branch of this \if block.  Update saved
! 			 * state so we will keep whatever data was put in query_buf by the
! 			 * active branch.
! 			 */
! 			save_query_text_state(scan_state, cstack, query_buf);
! 
! 			/*
! 			 * Discard \elif expression and ignore the rest until \endif.
! 			 * Switch state before reading expression to ensure proper lexer
! 			 * behavior.
! 			 */
! 			conditional_stack_poke(cstack, IFSTATE_IGNORED);
! 			ignore_boolean_expression(scan_state);
! 			break;
! 		case IFSTATE_FALSE:
! 
! 			/*
! 			 * Discard any query text added by the just-skipped branch.
! 			 */
! 			discard_query_text(scan_state, cstack, query_buf);
! 
! 			/*
! 			 * Have not yet found a true expression in this \if block, so this
! 			 * might be the first.  We have to change state before examining
! 			 * the expression, or the lexer won't do the right thing.
! 			 */
! 			conditional_stack_poke(cstack, IFSTATE_TRUE);
! 			if (!is_true_boolean_expression(scan_state, "\\elif expression"))
! 				conditional_stack_poke(cstack, IFSTATE_FALSE);
! 			break;
! 		case IFSTATE_IGNORED:
! 
! 			/*
! 			 * Discard any query text added by the just-skipped branch.
! 			 */
! 			discard_query_text(scan_state, cstack, query_buf);
! 
! 			/*
! 			 * Skip expression and move on.  Either the \if block already had
! 			 * an active section, or whole block is being skipped.
! 			 */
! 			ignore_boolean_expression(scan_state);
! 			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;
! 	}
! 
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \else -- final alternative in an \if..\endif block
!  *
!  * Statements within an \else branch will only be executed if
!  * all previous \if and \elif expressions evaluated to false
!  * and the block was not itself being ignored.
!  */
! static backslashResult
! exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
! 				  PQExpBuffer query_buf)
! {
! 	bool		success = true;
! 
! 	switch (conditional_stack_peek(cstack))
! 	{
! 		case IFSTATE_TRUE:
! 
! 			/*
! 			 * Just finished active branch of this \if block.  Update saved
! 			 * state so we will keep whatever data was put in query_buf by the
! 			 * active branch.
! 			 */
! 			save_query_text_state(scan_state, cstack, query_buf);
! 
! 			/* Now skip the \else branch */
! 			conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
! 			break;
! 		case IFSTATE_FALSE:
! 
! 			/*
! 			 * Discard any query text added by the just-skipped branch.
! 			 */
! 			discard_query_text(scan_state, cstack, query_buf);
! 
! 			/*
! 			 * We've not found any true \if or \elif expression, so execute
! 			 * the \else branch.
! 			 */
! 			conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE);
! 			break;
! 		case IFSTATE_IGNORED:
! 
! 			/*
! 			 * Discard any query text added by the just-skipped branch.
! 			 */
! 			discard_query_text(scan_state, cstack, query_buf);
! 
! 			/*
! 			 * Either we previously processed the active branch of this \if,
! 			 * or the whole \if block is being skipped.  Either way, skip the
! 			 * \else branch.
! 			 */
! 			conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
! 			break;
! 		case IFSTATE_ELSE_TRUE:
! 		case IFSTATE_ELSE_FALSE:
! 			psql_error("\\else: cannot occur after \\else\n");
! 			success = false;
! 			break;
! 		case IFSTATE_NONE:
! 			/* no \if to else from */
! 			psql_error("\\else: no matching \\if\n");
! 			success = false;
! 			break;
! 	}
! 
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \endif -- ends an \if...\endif block
!  */
! static backslashResult
! exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
! 				   PQExpBuffer query_buf)
! {
! 	bool		success = true;
! 
! 	switch (conditional_stack_peek(cstack))
! 	{
! 		case IFSTATE_TRUE:
! 		case IFSTATE_ELSE_TRUE:
! 			/* Close the \if block, keeping the query text */
! 			success = conditional_stack_pop(cstack);
! 			Assert(success);
! 			break;
! 		case IFSTATE_FALSE:
! 		case IFSTATE_IGNORED:
! 		case IFSTATE_ELSE_FALSE:
! 
! 			/*
! 			 * Discard any query text added by the just-skipped branch.
! 			 */
! 			discard_query_text(scan_state, cstack, query_buf);
! 
! 			/* Close the \if block */
! 			success = conditional_stack_pop(cstack);
! 			Assert(success);
! 			break;
! 		case IFSTATE_NONE:
! 			/* no \if to end */
! 			psql_error("\\endif: no matching \\if\n");
! 			success = false;
! 			break;
! 	}
! 
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \l -- list databases
!  */
! static backslashResult
! exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *pattern;
  		bool		show_verbose;
*************** exec_command(const char *cmd,
*** 1025,1035 ****
  		if (pattern)
  			free(pattern);
  	}
  
! 	/*
! 	 * large object things
! 	 */
! 	else if (strncmp(cmd, "lo_", 3) == 0)
  	{
  		char	   *opt1,
  				   *opt2;
--- 1717,1738 ----
  		if (pattern)
  			free(pattern);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \lo_* -- large object operations
!  */
! static backslashResult
! exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt1,
  				   *opt2;
*************** exec_command(const char *cmd,
*** 1087,1096 ****
  		free(opt1);
  		free(opt2);
  	}
  
  
! 	/* \o -- set query output */
! 	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_FILEPIPE, NULL, true);
--- 1790,1813 ----
  		free(opt1);
  		free(opt2);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
+ 	if (!success)
+ 		status = PSQL_CMD_ERROR;
  
! 	return status;
! }
! 
! /*
!  * \o -- set query output
!  */
! static backslashResult
! exec_command_out(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_FILEPIPE, NULL, true);
*************** exec_command(const char *cmd,
*** 1099,1107 ****
  		success = setQFout(fname);
  		free(fname);
  	}
  
! 	/* \p prints the current query buffer */
! 	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
  	{
  		if (query_buf && query_buf->len > 0)
  			puts(query_buf->data);
--- 1816,1835 ----
  		success = setQFout(fname);
  		free(fname);
  	}
+ 	else
+ 		ignore_slash_filepipe(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \p -- print the current query buffer
!  */
! static backslashResult
! exec_command_print(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf)
! {
! 	if (active_branch)
  	{
  		if (query_buf && query_buf->len > 0)
  			puts(query_buf->data);
*************** exec_command(const char *cmd,
*** 1110,1118 ****
  		fflush(stdout);
  	}
  
! 	/* \password -- set user password */
! 	else if (strcmp(cmd, "password") == 0)
  	{
  		char		pw1[100];
  		char		pw2[100];
  
--- 1838,1858 ----
  		fflush(stdout);
  	}
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \password -- set user password
!  */
! static backslashResult
! exec_command_password(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
+ 		char	   *opt0 = psql_scan_slash_option(scan_state,
+ 												  OT_SQLID, NULL, true);
  		char		pw1[100];
  		char		pw2[100];
  
*************** exec_command(const char *cmd,
*** 1126,1132 ****
  		}
  		else
  		{
- 			char	   *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
  			char	   *user;
  			char	   *encrypted_password;
  
--- 1866,1871 ----
*************** exec_command(const char *cmd,
*** 1159,1172 ****
  					PQclear(res);
  				PQfreemem(encrypted_password);
  			}
- 
- 			if (opt0)
- 				free(opt0);
  		}
  	}
  
! 	/* \prompt -- prompt and set variable */
! 	else if (strcmp(cmd, "prompt") == 0)
  	{
  		char	   *opt,
  				   *prompt_text = NULL;
--- 1898,1924 ----
  					PQclear(res);
  				PQfreemem(encrypted_password);
  			}
  		}
+ 
+ 		if (opt0)
+ 			free(opt0);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \prompt -- prompt and set variable
!  */
! static backslashResult
! exec_command_prompt(PsqlScanState scan_state, bool active_branch,
! 					const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt,
  				   *prompt_text = NULL;
*************** exec_command(const char *cmd,
*** 1225,1233 ****
  			free(opt);
  		}
  	}
  
! 	/* \pset -- set printing parameters */
! 	else if (strcmp(cmd, "pset") == 0)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
--- 1977,1997 ----
  			free(opt);
  		}
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \pset -- set printing parameters
!  */
! static backslashResult
! exec_command_pset(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1267,1279 ****
  		free(opt0);
  		free(opt1);
  	}
  
! 	/* \q or \quit */
! 	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
  		status = PSQL_CMD_TERMINATE;
  
! 	/* reset(clear) the buffer */
! 	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
  	{
  		resetPQExpBuffer(query_buf);
  		psql_scan_reset(scan_state);
--- 2031,2064 ----
  		free(opt0);
  		free(opt1);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \q or \quit -- exit psql
!  */
! static backslashResult
! exec_command_quit(PsqlScanState scan_state, bool active_branch)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  		status = PSQL_CMD_TERMINATE;
  
! 	return status;
! }
! 
! /*
!  * \r -- reset (clear) the query buffer
!  */
! static backslashResult
! exec_command_reset(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf)
! {
! 	if (active_branch)
  	{
  		resetPQExpBuffer(query_buf);
  		psql_scan_reset(scan_state);
*************** exec_command(const char *cmd,
*** 1281,1288 ****
  			puts(_("Query buffer reset (cleared)."));
  	}
  
! 	/* \s save history in a file or show it on the screen */
! 	else if (strcmp(cmd, "s") == 0)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
--- 2066,2083 ----
  			puts(_("Query buffer reset (cleared)."));
  	}
  
! 	return PSQL_CMD_SKIP_LINE;
! }
! 
! /*
!  * \s -- save history in a file or show it on the screen
!  */
! static backslashResult
! exec_command_s(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1295,1303 ****
  			putchar('\n');
  		free(fname);
  	}
  
! 	/* \set -- generalized set variable/option command */
! 	else if (strcmp(cmd, "set") == 0)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
--- 2090,2110 ----
  			putchar('\n');
  		free(fname);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \set -- set variable
!  */
! static backslashResult
! exec_command_set(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1336,1345 ****
  		}
  		free(opt0);
  	}
  
  
! 	/* \setenv -- set environment command */
! 	else if (strcmp(cmd, "setenv") == 0)
  	{
  		char	   *envvar = psql_scan_slash_option(scan_state,
  													OT_NORMAL, NULL, false);
--- 2143,2164 ----
  		}
  		free(opt0);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
+ 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+ }
  
! /*
!  * \setenv -- set environment variable
!  */
! static backslashResult
! exec_command_setenv(PsqlScanState scan_state, bool active_branch,
! 					const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *envvar = psql_scan_slash_option(scan_state,
  													OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1381,1389 ****
  		free(envvar);
  		free(envval);
  	}
  
! 	/* \sf -- show a function's source code */
! 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
  	{
  		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
  		PQExpBuffer func_buf;
--- 2200,2220 ----
  		free(envvar);
  		free(envval);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \sf -- show a function's source code
!  */
! static backslashResult
! exec_command_sf(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		bool		show_linenumbers = (strcmp(cmd, "sf+") == 0);
  		PQExpBuffer func_buf;
*************** exec_command(const char *cmd,
*** 1463,1471 ****
  			free(func);
  		destroyPQExpBuffer(func_buf);
  	}
  
! 	/* \sv -- show a view's source code */
! 	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
  	{
  		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
  		PQExpBuffer view_buf;
--- 2294,2314 ----
  			free(func);
  		destroyPQExpBuffer(func_buf);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \sv -- show a view's source code
!  */
! static backslashResult
! exec_command_sv(PsqlScanState scan_state, bool active_branch, const char *cmd)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
  		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
  		PQExpBuffer view_buf;
*************** exec_command(const char *cmd,
*** 1539,1547 ****
  			free(view);
  		destroyPQExpBuffer(view_buf);
  	}
  
! 	/* \t -- turn off headers and row count */
! 	else if (strcmp(cmd, "t") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
--- 2382,2402 ----
  			free(view);
  		destroyPQExpBuffer(view_buf);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \t -- turn off table headers and row count
!  */
! static backslashResult
! exec_command_t(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1549,1557 ****
  		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
  
! 	/* \T -- define html <table ...> attributes */
! 	else if (strcmp(cmd, "T") == 0)
  	{
  		char	   *value = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, false);
--- 2404,2424 ----
  		success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \T -- define html <table ...> attributes
!  */
! static backslashResult
! exec_command_T(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *value = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1559,1567 ****
  		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
  		free(value);
  	}
  
! 	/* \timing -- toggle timing of queries */
! 	else if (strcmp(cmd, "timing") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, false);
--- 2426,2446 ----
  		success = do_pset("tableattr", value, &pset.popt, pset.quiet);
  		free(value);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \timing -- enable/disable timing of queries
!  */
! static backslashResult
! exec_command_timing(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1579,1587 ****
  		}
  		free(opt);
  	}
  
! 	/* \unset */
! 	else if (strcmp(cmd, "unset") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, false);
--- 2458,2479 ----
  		}
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \unset -- unset variable
!  */
! static backslashResult
! exec_command_unset(PsqlScanState scan_state, bool active_branch,
! 				   const char *cmd)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1596,1608 ****
  
  		free(opt);
  	}
  
! 	/* \w -- write query buffer to file */
! 	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
  	{
  		FILE	   *fd = NULL;
  		bool		is_pipe = false;
- 		char	   *fname = NULL;
  
  		if (!query_buf)
  		{
--- 2488,2515 ----
  
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \w -- write query buffer to file
!  */
! static backslashResult
! exec_command_write(PsqlScanState scan_state, bool active_branch,
! 				   const char *cmd,
! 				   PQExpBuffer query_buf, PQExpBuffer previous_buf)
! {
! 	backslashResult status = PSQL_CMD_SKIP_LINE;
! 
! 	if (active_branch)
  	{
+ 		char	   *fname = psql_scan_slash_option(scan_state,
+ 												   OT_FILEPIPE, NULL, true);
  		FILE	   *fd = NULL;
  		bool		is_pipe = false;
  
  		if (!query_buf)
  		{
*************** exec_command(const char *cmd,
*** 1611,1627 ****
  		}
  		else
  		{
- 			fname = psql_scan_slash_option(scan_state,
- 										   OT_FILEPIPE, NULL, true);
- 			expand_tilde(&fname);
- 
  			if (!fname)
  			{
  				psql_error("\\%s: missing required argument\n", cmd);
! 				success = false;
  			}
  			else
  			{
  				if (fname[0] == '|')
  				{
  					is_pipe = true;
--- 2518,2531 ----
  		}
  		else
  		{
  			if (!fname)
  			{
  				psql_error("\\%s: missing required argument\n", cmd);
! 				status = PSQL_CMD_ERROR;
  			}
  			else
  			{
+ 				expand_tilde(&fname);
  				if (fname[0] == '|')
  				{
  					is_pipe = true;
*************** exec_command(const char *cmd,
*** 1636,1642 ****
  				if (!fd)
  				{
  					psql_error("%s: %s\n", fname, strerror(errno));
! 					success = false;
  				}
  			}
  		}
--- 2540,2546 ----
  				if (!fd)
  				{
  					psql_error("%s: %s\n", fname, strerror(errno));
! 					status = PSQL_CMD_ERROR;
  				}
  			}
  		}
*************** exec_command(const char *cmd,
*** 1647,1652 ****
--- 2551,2559 ----
  
  			if (query_buf && query_buf->len > 0)
  				fprintf(fd, "%s\n", query_buf->data);
+ 			/* Applies to previous query if current buffer is empty */
+ 			else if (previous_buf && previous_buf->len > 0)
+ 				fprintf(fd, "%s\n", previous_buf->data);
  
  			if (is_pipe)
  				result = pclose(fd);
*************** exec_command(const char *cmd,
*** 1656,1662 ****
  			if (result == EOF)
  			{
  				psql_error("%s: %s\n", fname, strerror(errno));
! 				success = false;
  			}
  		}
  
--- 2563,2569 ----
  			if (result == EOF)
  			{
  				psql_error("%s: %s\n", fname, strerror(errno));
! 				status = PSQL_CMD_ERROR;
  			}
  		}
  
*************** exec_command(const char *cmd,
*** 1665,1673 ****
  
  		free(fname);
  	}
  
! 	/* \watch -- execute a query every N seconds */
! 	else if (strcmp(cmd, "watch") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
--- 2572,2593 ----
  
  		free(fname);
  	}
+ 	else
+ 		ignore_slash_filepipe(scan_state);
  
! 	return status;
! }
! 
! /*
!  * \watch -- execute a query every N seconds
!  */
! static backslashResult
! exec_command_watch(PsqlScanState scan_state, bool active_branch,
! 				   PQExpBuffer query_buf, PQExpBuffer previous_buf)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1682,1696 ****
  			free(opt);
  		}
  
  		success = do_watch(query_buf, sleep);
  
  		/* Reset the query buffer as though for \r */
  		resetPQExpBuffer(query_buf);
  		psql_scan_reset(scan_state);
  	}
  
! 	/* \x -- set or toggle expanded table representation */
! 	else if (strcmp(cmd, "x") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
--- 2602,2631 ----
  			free(opt);
  		}
  
+ 		/* Applies to previous query if current buffer is empty */
+ 		copy_previous_query(query_buf, previous_buf);
+ 
  		success = do_watch(query_buf, sleep);
  
  		/* Reset the query buffer as though for \r */
  		resetPQExpBuffer(query_buf);
  		psql_scan_reset(scan_state);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \x -- set or toggle expanded table representation
!  */
! static backslashResult
! exec_command_x(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1698,1706 ****
  		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
  
! 	/* \z -- list table rights (equivalent to \dp) */
! 	else if (strcmp(cmd, "z") == 0)
  	{
  		char	   *pattern = psql_scan_slash_option(scan_state,
  													 OT_NORMAL, NULL, true);
--- 2633,2653 ----
  		success = do_pset("expanded", opt, &pset.popt, pset.quiet);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \z -- list table privileges (equivalent to \dp)
!  */
! static backslashResult
! exec_command_z(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *pattern = psql_scan_slash_option(scan_state,
  													 OT_NORMAL, NULL, true);
*************** exec_command(const char *cmd,
*** 1709,1717 ****
  		if (pattern)
  			free(pattern);
  	}
  
! 	/* \! -- shell escape */
! 	else if (strcmp(cmd, "!") == 0)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
--- 2656,2676 ----
  		if (pattern)
  			free(pattern);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \! -- execute shell command
!  */
! static backslashResult
! exec_command_shell_escape(PsqlScanState scan_state, bool active_branch)
! {
! 	bool		success = true;
! 
! 	if (active_branch)
  	{
  		char	   *opt = psql_scan_slash_option(scan_state,
  												 OT_WHOLE_LINE, NULL, false);
*************** exec_command(const char *cmd,
*** 1719,1727 ****
  		success = do_shell(opt);
  		free(opt);
  	}
  
! 	/* \? -- slash command help */
! 	else if (strcmp(cmd, "?") == 0)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
--- 2678,2696 ----
  		success = do_shell(opt);
  		free(opt);
  	}
+ 	else
+ 		ignore_slash_whole_line(scan_state);
  
! 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
! }
! 
! /*
!  * \? -- print help about backslash commands
!  */
! static backslashResult
! exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch)
! {
! 	if (active_branch)
  	{
  		char	   *opt0 = psql_scan_slash_option(scan_state,
  												  OT_NORMAL, NULL, false);
*************** exec_command(const char *cmd,
*** 1734,1767 ****
  			helpVariables(pset.popt.topt.pager);
  		else
  			slashUsage(pset.popt.topt.pager);
  	}
  
! #if 0
  
  	/*
! 	 * These commands don't do anything. I just use them to test the parser.
  	 */
! 	else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
! 	{
! 		int			i = 0;
! 		char	   *value;
  
! 		while ((value = psql_scan_slash_option(scan_state,
! 											   OT_NORMAL, NULL, true)))
! 		{
! 			psql_error("+ opt(%d) = |%s|\n", i++, value);
! 			free(value);
! 		}
  	}
- #endif
  
! 	else
! 		status = PSQL_CMD_UNKNOWN;
  
! 	if (!success)
! 		status = PSQL_CMD_ERROR;
  
! 	return status;
  }
  
  /*
--- 2703,2933 ----
  			helpVariables(pset.popt.topt.pager);
  		else
  			slashUsage(pset.popt.topt.pager);
+ 
+ 		if (opt0)
+ 			free(opt0);
  	}
+ 	else
+ 		ignore_slash_options(scan_state);
+ 
+ 	return PSQL_CMD_SKIP_LINE;
+ }
  
! 
! /*
!  * Read and interpret an argument to the \connect slash command.
!  */
! static char *
! read_connect_arg(PsqlScanState scan_state)
! {
! 	char	   *result;
! 	char		quote;
  
  	/*
! 	 * Ideally we should treat the arguments as SQL identifiers.  But for
! 	 * backwards compatibility with 7.2 and older pg_dump files, we have to
! 	 * take unquoted arguments verbatim (don't downcase them). For now,
! 	 * double-quoted arguments may be stripped of double quotes (as if SQL
! 	 * identifiers).  By 7.4 or so, pg_dump files can be expected to
! 	 * double-quote all mixed-case \connect arguments, and then we can get rid
! 	 * of OT_SQLIDHACK.
  	 */
! 	result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
  
! 	if (!result)
! 		return NULL;
! 
! 	if (quote)
! 		return result;
! 
! 	if (*result == '\0' || strcmp(result, "-") == 0)
! 		return NULL;
! 
! 	return result;
! }
! 
! /*
!  * Read a boolean expression, return it as a PQExpBuffer string.
!  *
!  * Note: anything more or less than one token will certainly fail to be
!  * parsed by ParseVariableBool, so we don't worry about complaining here.
!  * This routine's return data structure will need to be rethought anyway
!  * to support likely future extensions such as "\if defined VARNAME".
!  */
! static PQExpBuffer
! gather_boolean_expression(PsqlScanState scan_state)
! {
! 	PQExpBuffer exp_buf = createPQExpBuffer();
! 	int			num_options = 0;
! 	char	   *value;
! 
! 	/* collect all arguments for the conditional command into exp_buf */
! 	while ((value = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, false)) != NULL)
! 	{
! 		/* add spaces between tokens */
! 		if (num_options > 0)
! 			appendPQExpBufferChar(exp_buf, ' ');
! 		appendPQExpBufferStr(exp_buf, value);
! 		num_options++;
! 		free(value);
  	}
  
! 	return exp_buf;
! }
  
! /*
!  * Read a boolean expression, return true if the expression
!  * was a valid boolean expression that evaluated to true.
!  * Otherwise return false.
!  *
!  * Note: conditional stack's top state must be active, else lexer will
!  * fail to expand variables and backticks.
!  */
! static bool
! is_true_boolean_expression(PsqlScanState scan_state, const char *name)
! {
! 	PQExpBuffer buf = gather_boolean_expression(scan_state);
! 	bool		value = false;
! 	bool		success = ParseVariableBool(buf->data, name, &value);
  
! 	destroyPQExpBuffer(buf);
! 	return success && value;
! }
! 
! /*
!  * Read a boolean expression, but do nothing with it.
!  *
!  * Note: conditional stack's top state must be INACTIVE, else lexer will
!  * expand variables and backticks, which we do not want here.
!  */
! static void
! ignore_boolean_expression(PsqlScanState scan_state)
! {
! 	PQExpBuffer buf = gather_boolean_expression(scan_state);
! 
! 	destroyPQExpBuffer(buf);
! }
! 
! /*
!  * Read and discard "normal" slash command options.
!  *
!  * This should be used for inactive-branch processing of any slash command
!  * that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters.
!  * We don't need to worry about exactly how many it would eat, since the
!  * cleanup logic in HandleSlashCmds would silently discard any extras anyway.
!  */
! static void
! ignore_slash_options(PsqlScanState scan_state)
! {
! 	char	   *arg;
! 
! 	while ((arg = psql_scan_slash_option(scan_state,
! 										 OT_NORMAL, NULL, false)) != NULL)
! 		free(arg);
! }
! 
! /*
!  * Read and discard FILEPIPE slash command argument.
!  *
!  * This *MUST* be used for inactive-branch processing of any slash command
!  * that takes an OT_FILEPIPE option.  Otherwise we might consume a different
!  * amount of option text in active and inactive cases.
!  */
! static void
! ignore_slash_filepipe(PsqlScanState scan_state)
! {
! 	char	   *arg = psql_scan_slash_option(scan_state,
! 											 OT_FILEPIPE, NULL, false);
! 
! 	if (arg)
! 		free(arg);
! }
! 
! /*
!  * Read and discard whole-line slash command argument.
!  *
!  * This *MUST* be used for inactive-branch processing of any slash command
!  * that takes an OT_WHOLE_LINE option.  Otherwise we might consume a different
!  * amount of option text in active and inactive cases.
!  */
! static void
! ignore_slash_whole_line(PsqlScanState scan_state)
! {
! 	char	   *arg = psql_scan_slash_option(scan_state,
! 											 OT_WHOLE_LINE, NULL, false);
! 
! 	if (arg)
! 		free(arg);
! }
! 
! /*
!  * 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);
! }
! 
! /*
!  * Prepare to possibly restore query buffer to its current state
!  * (cf. discard_query_text).
!  *
!  * We need to remember the length of the query buffer, and the lexer's
!  * notion of the parenthesis nesting depth.
!  */
! static void
! save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
! 					  PQExpBuffer query_buf)
! {
! 	if (query_buf)
! 		conditional_stack_set_query_len(cstack, query_buf->len);
! 	conditional_stack_set_paren_depth(cstack,
! 									  psql_scan_get_paren_depth(scan_state));
! }
! 
! /*
!  * Discard any query text absorbed during an inactive conditional branch.
!  *
!  * We must discard data that was appended to query_buf during an inactive
!  * \if branch.  We don't have to do anything there if there's no query_buf.
!  *
!  * Also, reset the lexer state to the same paren depth there was before.
!  * (The rest of its state doesn't need attention, since we could not be
!  * inside a comment or literal or partial token.)
!  */
! static void
! discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
! 				   PQExpBuffer query_buf)
! {
! 	if (query_buf)
! 	{
! 		int			new_len = conditional_stack_get_query_len(cstack);
! 
! 		Assert(new_len >= 0 && new_len <= query_buf->len);
! 		query_buf->len = new_len;
! 		query_buf->data[new_len] = '\0';
! 	}
! 	psql_scan_set_paren_depth(scan_state,
! 							  conditional_stack_get_paren_depth(cstack));
! }
! 
! /*
!  * If query_buf is empty, copy previous_buf into it.
!  *
!  * This is used by various slash commands for which re-execution of a
!  * previous query is a common usage.  For convenience, we allow the
!  * case of query_buf == NULL (and do nothing).
!  */
! static void
! copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
! {
! 	if (query_buf && query_buf->len == 0)
! 		appendPQExpBufferStr(query_buf, previous_buf->data);
  }
  
  /*
diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h
index d0c3264..e8ea847 100644
*** a/src/bin/psql/command.h
--- b/src/bin/psql/command.h
***************
*** 10,15 ****
--- 10,16 ----
  
  #include "fe_utils/print.h"
  #include "fe_utils/psqlscan.h"
+ #include "conditional.h"
  
  
  typedef enum _backslashResult
*************** typedef enum _backslashResult
*** 25,31 ****
  
  
  extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
! 				PQExpBuffer query_buf);
  
  extern int	process_file(char *filename, bool use_relative_path);
  
--- 26,34 ----
  
  
  extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
! 				ConditionalStack cstack,
! 				PQExpBuffer query_buf,
! 				PQExpBuffer previous_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 e9d4fe6..b06ae97 100644
*** a/src/bin/psql/common.c
--- b/src/bin/psql/common.c
*************** setQFout(const char *fname)
*** 121,127 ****
   * (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.
   */
  char *
  psql_get_variable(const char *varname, bool escape, bool as_ident,
--- 121,128 ----
   * (Failure in escaping should lead to returning NULL.)
   *
   * "passthrough" is the pointer previously given to psql_scan_set_passthrough.
!  * In psql, passthrough points to a ConditionalStack, which we check to
!  * determine whether variable expansion is allowed.
   */
  char *
  psql_get_variable(const char *varname, bool escape, bool as_ident,
*************** psql_get_variable(const char *varname, b
*** 130,135 ****
--- 131,140 ----
  	char	   *result;
  	const char *value;
  
+ 	/* In an inactive \if branch, suppress all variable substitutions */
+ 	if (passthrough && !conditional_active((ConditionalStack) passthrough))
+ 		return NULL;
+ 
  	value = GetVariable(pset.vars, varname);
  	if (!value)
  		return NULL;
diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c
index ...63977ce .
*** a/src/bin/psql/conditional.c
--- b/src/bin/psql/conditional.c
***************
*** 0 ****
--- 1,153 ----
+ /*
+  * 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(void)
+ {
+ 	ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+ 
+ 	cstack->head = NULL;
+ 	return cstack;
+ }
+ 
+ /*
+  * destroy stack
+  */
+ void
+ conditional_stack_destroy(ConditionalStack cstack)
+ {
+ 	while (conditional_stack_pop(cstack))
+ 		continue;
+ 	free(cstack);
+ }
+ 
+ /*
+  * Create a new conditional branch.
+  */
+ void
+ conditional_stack_push(ConditionalStack cstack, ifState new_state)
+ {
+ 	IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
+ 
+ 	p->if_state = new_state;
+ 	p->query_len = -1;
+ 	p->paren_depth = -1;
+ 	p->next = cstack->head;
+ 	cstack->head = p;
+ }
+ 
+ /*
+  * Destroy the topmost conditional branch.
+  * Returns false if there was no branch to end.
+  */
+ bool
+ conditional_stack_pop(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(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 no branch state to set.
+  */
+ bool
+ conditional_stack_poke(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-blocks.
+  */
+ bool
+ conditional_stack_empty(ConditionalStack cstack)
+ {
+ 	return cstack->head == NULL;
+ }
+ 
+ /*
+  * True if we should execute commands normally; that is, the current
+  * conditional branch is active, or there is no open \if block.
+  */
+ bool
+ conditional_active(ConditionalStack cstack)
+ {
+ 	ifState		s = conditional_stack_peek(cstack);
+ 
+ 	return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+ }
+ 
+ /*
+  * Save current query buffer length in topmost stack entry.
+  */
+ void
+ conditional_stack_set_query_len(ConditionalStack cstack, int len)
+ {
+ 	Assert(!conditional_stack_empty(cstack));
+ 	cstack->head->query_len = len;
+ }
+ 
+ /*
+  * Fetch last-recorded query buffer length from topmost stack entry.
+  * Will return -1 if no stack or it was never saved.
+  */
+ int
+ conditional_stack_get_query_len(ConditionalStack cstack)
+ {
+ 	if (conditional_stack_empty(cstack))
+ 		return -1;
+ 	return cstack->head->query_len;
+ }
+ 
+ /*
+  * Save current parenthesis nesting depth in topmost stack entry.
+  */
+ void
+ conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
+ {
+ 	Assert(!conditional_stack_empty(cstack));
+ 	cstack->head->paren_depth = depth;
+ }
+ 
+ /*
+  * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
+  * Will return -1 if no stack or it was never saved.
+  */
+ int
+ conditional_stack_get_paren_depth(ConditionalStack cstack)
+ {
+ 	if (conditional_stack_empty(cstack))
+ 		return -1;
+ 	return cstack->head->paren_depth;
+ }
diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h
index ...90e4d93 .
*** a/src/bin/psql/conditional.h
--- b/src/bin/psql/conditional.h
***************
*** 0 ****
--- 1,83 ----
+ /*
+  * psql - the PostgreSQL interactive terminal
+  *
+  * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+  *
+  * src/bin/psql/conditional.h
+  */
+ #ifndef CONDITIONAL_H
+ #define CONDITIONAL_H
+ 
+ /*
+  * Possible states of a single level of \if block.
+  */
+ typedef enum ifState
+ {
+ 	IFSTATE_NONE = 0,			/* not currently in an \if block */
+ 	IFSTATE_TRUE,				/* currently in an \if or \elif that is true
+ 								 * and all parent branches (if any) are true */
+ 	IFSTATE_FALSE,				/* currently in an \if or \elif that is false
+ 								 * but no true branch has yet been seen, and
+ 								 * all parent branches (if any) are true */
+ 	IFSTATE_IGNORED,			/* currently in an \elif that follows a true
+ 								 * branch, or the whole \if is a child of a
+ 								 * false parent branch */
+ 	IFSTATE_ELSE_TRUE,			/* currently in an \else that is true and all
+ 								 * parent branches (if any) are true */
+ 	IFSTATE_ELSE_FALSE			/* currently in an \else that is false or
+ 								 * ignored */
+ } ifState;
+ 
+ /*
+  * The state of nested \ifs is stored in a stack.
+  *
+  * query_len is used to determine what accumulated text to throw away at the
+  * end of an inactive branch.  (We could, perhaps, teach the lexer to not add
+  * stuff to the query buffer in the first place when inside an inactive branch;
+  * but that would be very invasive.)  We also need to save and restore the
+  * lexer's parenthesis nesting depth when throwing away text.  (We don't need
+  * to save and restore any of its other state, such as comment nesting depth,
+  * because a backslash command could never appear inside a comment or SQL
+  * literal.)
+  */
+ typedef struct IfStackElem
+ {
+ 	ifState		if_state;		/* current state, see enum above */
+ 	int			query_len;		/* length of query_buf at last branch start */
+ 	int			paren_depth;	/* parenthesis depth at last branch start */
+ 	struct IfStackElem *next;	/* next surrounding \if, if any */
+ } IfStackElem;
+ 
+ typedef struct ConditionalStackData
+ {
+ 	IfStackElem *head;
+ } ConditionalStackData;
+ 
+ typedef struct ConditionalStackData *ConditionalStack;
+ 
+ 
+ extern ConditionalStack conditional_stack_create(void);
+ 
+ extern void conditional_stack_destroy(ConditionalStack cstack);
+ 
+ extern void 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_stack_empty(ConditionalStack cstack);
+ 
+ extern bool conditional_active(ConditionalStack cstack);
+ 
+ extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
+ 
+ extern int	conditional_stack_get_query_len(ConditionalStack cstack);
+ 
+ extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth);
+ 
+ extern int	conditional_stack_get_paren_depth(ConditionalStack cstack);
+ 
+ #endif   /* CONDITIONAL_H */
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
*************** handleCopyIn(PGconn *conn, FILE *copystr
*** 552,558 ****
  		/* interactive input probably silly, but give one prompt anyway */
  		if (showprompt)
  		{
! 			const char *prompt = get_prompt(PROMPT_COPY);
  
  			fputs(prompt, stdout);
  			fflush(stdout);
--- 552,558 ----
  		/* interactive input probably silly, but give one prompt anyway */
  		if (showprompt)
  		{
! 			const char *prompt = get_prompt(PROMPT_COPY, NULL);
  
  			fputs(prompt, stdout);
  			fflush(stdout);
*************** handleCopyIn(PGconn *conn, FILE *copystr
*** 590,596 ****
  
  			if (showprompt)
  			{
! 				const char *prompt = get_prompt(PROMPT_COPY);
  
  				fputs(prompt, stdout);
  				fflush(stdout);
--- 590,596 ----
  
  			if (showprompt)
  			{
! 				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..ac43522 100644
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
*************** slashUsage(unsigned short int pager)
*** 167,173 ****
  	 * Use "psql --help=commands | wc" to count correctly.  It's okay to count
  	 * the USE_READLINE line even in builds without that.
  	 */
! 	output = PageOutput(113, pager ? &(pset.popt.topt) : NULL);
  
  	fprintf(output, _("General\n"));
  	fprintf(output, _("  \\copyright             show PostgreSQL usage and distribution terms\n"));
--- 167,173 ----
  	 * Use "psql --help=commands | wc" to count correctly.  It's okay to count
  	 * the USE_READLINE line even in builds without that.
  	 */
! 	output = PageOutput(122, pager ? &(pset.popt.topt) : NULL);
  
  	fprintf(output, _("General\n"));
  	fprintf(output, _("  \\copyright             show PostgreSQL usage and distribution terms\n"));
*************** slashUsage(unsigned short int pager)
*** 210,215 ****
--- 210,222 ----
  	fprintf(output, _("  \\qecho [STRING]        write string to query output stream (see \\o)\n"));
  	fprintf(output, "\n");
  
+ 	fprintf(output, _("Conditional\n"));
+ 	fprintf(output, _("  \\if EXPR               begin conditional block\n"));
+ 	fprintf(output, _("  \\elif EXPR             alternative within current conditional block\n"));
+ 	fprintf(output, _("  \\else                  final alternative within current conditional block\n"));
+ 	fprintf(output, _("  \\endif                 end 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..2bc2f43 100644
*** a/src/bin/psql/mainloop.c
--- b/src/bin/psql/mainloop.c
*************** int
*** 35,40 ****
--- 35,41 ----
  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,
*************** MainLoop(FILE *source)
*** 50,65 ****
  	volatile promptStatus_t prompt_status = PROMPT_READY;
  	volatile int count_eof = 0;
  	volatile bool die_on_error = false;
- 
- 	/* Save the prior command source */
  	FILE	   *prev_cmd_source;
  	bool		prev_cmd_interactive;
  	uint64		prev_lineno;
  
! 	/* Save old settings */
  	prev_cmd_source = pset.cur_cmd_source;
  	prev_cmd_interactive = pset.cur_cmd_interactive;
  	prev_lineno = pset.lineno;
  
  	/* Establish new source */
  	pset.cur_cmd_source = source;
--- 51,65 ----
  	volatile promptStatus_t prompt_status = PROMPT_READY;
  	volatile int count_eof = 0;
  	volatile bool die_on_error = false;
  	FILE	   *prev_cmd_source;
  	bool		prev_cmd_interactive;
  	uint64		prev_lineno;
  
! 	/* Save the prior command source */
  	prev_cmd_source = pset.cur_cmd_source;
  	prev_cmd_interactive = pset.cur_cmd_interactive;
  	prev_lineno = pset.lineno;
+ 	/* pset.stmt_lineno does not need to be saved and restored */
  
  	/* Establish new source */
  	pset.cur_cmd_source = source;
*************** MainLoop(FILE *source)
*** 69,74 ****
--- 69,76 ----
  
  	/* 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();
*************** MainLoop(FILE *source)
*** 122,128 ****
--- 124,142 ----
  			cancel_pressed = false;
  
  			if (pset.cur_cmd_interactive)
+ 			{
  				putc('\n', stdout);
+ 
+ 				/*
+ 				 * if interactive user is in an \if block, then Ctrl-C will
+ 				 * exit from the innermost \if.
+ 				 */
+ 				if (!conditional_stack_empty(cond_stack))
+ 				{
+ 					psql_error("\\if: escaped\n");
+ 					conditional_stack_pop(cond_stack);
+ 				}
+ 			}
  			else
  			{
  				successResult = EXIT_USER;
*************** MainLoop(FILE *source)
*** 140,146 ****
  			/* 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);
  		}
  		else
  		{
--- 154,161 ----
  			/* 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, cond_stack),
! 									query_buf);
  		}
  		else
  		{
*************** MainLoop(FILE *source)
*** 286,293 ****
  				(scan_result == PSCAN_EOL && pset.singleline))
  			{
  				/*
! 				 * Save query in history.  We use history_buf to accumulate
! 				 * multi-line queries into a single history entry.
  				 */
  				if (pset.cur_cmd_interactive && !line_saved_in_history)
  				{
--- 301,310 ----
  				(scan_result == PSCAN_EOL && pset.singleline))
  			{
  				/*
! 				 * Save line in history.  We use history_buf to accumulate
! 				 * multi-line queries into a single history entry.  Note that
! 				 * history accumulation works on input lines, so it doesn't
! 				 * matter whether the query will be ignored due to \if.
  				 */
  				if (pset.cur_cmd_interactive && !line_saved_in_history)
  				{
*************** MainLoop(FILE *source)
*** 296,317 ****
  					line_saved_in_history = true;
  				}
  
! 				/* execute query */
! 				success = SendQuery(query_buf->data);
! 				slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
! 				pset.stmt_lineno = 1;
! 
! 				/* transfer query to previous_buf by pointer-swapping */
  				{
! 					PQExpBuffer swap_buf = previous_buf;
  
! 					previous_buf = query_buf;
! 					query_buf = swap_buf;
! 				}
! 				resetPQExpBuffer(query_buf);
  
! 				added_nl_pos = -1;
! 				/* we need not do psql_scan_reset() here */
  			}
  			else if (scan_result == PSCAN_BACKSLASH)
  			{
--- 313,348 ----
  					line_saved_in_history = true;
  				}
  
! 				/* execute query unless we're in an inactive \if branch */
! 				if (conditional_active(cond_stack))
  				{
! 					success = SendQuery(query_buf->data);
! 					slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
! 					pset.stmt_lineno = 1;
  
! 					/* transfer query to previous_buf by pointer-swapping */
! 					{
! 						PQExpBuffer swap_buf = previous_buf;
  
! 						previous_buf = query_buf;
! 						query_buf = swap_buf;
! 					}
! 					resetPQExpBuffer(query_buf);
! 
! 					added_nl_pos = -1;
! 					/* we need not do psql_scan_reset() here */
! 				}
! 				else
! 				{
! 					/* if interactive, warn about non-executed query */
! 					if (pset.cur_cmd_interactive)
! 						psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
! 					/* fake an OK result for purposes of loop checks */
! 					success = true;
! 					slashCmdStatus = PSQL_CMD_SEND;
! 					pset.stmt_lineno = 1;
! 					/* note that query_buf doesn't change state */
! 				}
  			}
  			else if (scan_result == PSCAN_BACKSLASH)
  			{
*************** MainLoop(FILE *source)
*** 343,363 ****
  
  				/* execute backslash command */
  				slashCmdStatus = HandleSlashCmds(scan_state,
! 												 query_buf->len > 0 ?
! 												 query_buf : previous_buf);
  
  				success = slashCmdStatus != PSQL_CMD_ERROR;
- 				pset.stmt_lineno = 1;
  
! 				if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
! 					query_buf->len == 0)
! 				{
! 					/* copy previous buffer to current for handling */
! 					appendPQExpBufferStr(query_buf, previous_buf->data);
! 				}
  
  				if (slashCmdStatus == PSQL_CMD_SEND)
  				{
  					success = SendQuery(query_buf->data);
  
  					/* transfer query to previous_buf by pointer-swapping */
--- 374,397 ----
  
  				/* execute backslash command */
  				slashCmdStatus = HandleSlashCmds(scan_state,
! 												 cond_stack,
! 												 query_buf,
! 												 previous_buf);
  
  				success = slashCmdStatus != PSQL_CMD_ERROR;
  
! 				/*
! 				 * Resetting stmt_lineno after a backslash command isn't
! 				 * always appropriate, but it's what we've done historically
! 				 * and there have been few complaints.
! 				 */
! 				pset.stmt_lineno = 1;
  
  				if (slashCmdStatus == PSQL_CMD_SEND)
  				{
+ 					/* should not see this in inactive branch */
+ 					Assert(conditional_active(cond_stack));
+ 
  					success = SendQuery(query_buf->data);
  
  					/* transfer query to previous_buf by pointer-swapping */
*************** MainLoop(FILE *source)
*** 374,379 ****
--- 408,415 ----
  				}
  				else if (slashCmdStatus == PSQL_CMD_NEWEDIT)
  				{
+ 					/* should not see this in inactive branch */
+ 					Assert(conditional_active(cond_stack));
  					/* rescan query_buf as new input */
  					psql_scan_finish(scan_state);
  					free(line);
*************** MainLoop(FILE *source)
*** 429,436 ****
  		if (pset.cur_cmd_interactive)
  			pg_send_history(history_buf);
  
! 		/* execute query */
! 		success = SendQuery(query_buf->data);
  
  		if (!success && die_on_error)
  			successResult = EXIT_USER;
--- 465,481 ----
  		if (pset.cur_cmd_interactive)
  			pg_send_history(history_buf);
  
! 		/* execute query unless we're in an inactive \if branch */
! 		if (conditional_active(cond_stack))
! 		{
! 			success = SendQuery(query_buf->data);
! 		}
! 		else
! 		{
! 			if (pset.cur_cmd_interactive)
! 				psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
! 			success = true;
! 		}
  
  		if (!success && die_on_error)
  			successResult = EXIT_USER;
*************** MainLoop(FILE *source)
*** 439,444 ****
--- 484,502 ----
  	}
  
  	/*
+ 	 * 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
*************** MainLoop(FILE *source)
*** 452,457 ****
--- 510,516 ----
  	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,72 ****
   */
  
  char *
! get_prompt(promptStatus_t status)
  {
  #define MAX_PROMPT_SIZE 256
  	static char destination[MAX_PROMPT_SIZE + 1];
--- 66,72 ----
   */
  
  char *
! get_prompt(promptStatus_t status, ConditionalStack cstack)
  {
  #define MAX_PROMPT_SIZE 256
  	static char destination[MAX_PROMPT_SIZE + 1];
*************** get_prompt(promptStatus_t status)
*** 188,194 ****
  					switch (status)
  					{
  						case PROMPT_READY:
! 							if (!pset.db)
  								buf[0] = '!';
  							else if (!pset.singleline)
  								buf[0] = '=';
--- 188,196 ----
  					switch (status)
  					{
  						case PROMPT_READY:
! 							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,16 ****
  
  /* enum promptStatus_t is now defined by psqlscan.h */
  #include "fe_utils/psqlscan.h"
  
! char	   *get_prompt(promptStatus_t status);
  
  #endif   /* PROMPT_H */
--- 10,17 ----
  
  /* enum promptStatus_t is now defined by psqlscan.h */
  #include "fe_utils/psqlscan.h"
+ #include "conditional.h"
  
! char	   *get_prompt(promptStatus_t status, ConditionalStack cstack);
  
  #endif   /* PROMPT_H */
diff --git a/src/bin/psql/psqlscanslash.h b/src/bin/psql/psqlscanslash.h
index 266e93a..db76061 100644
*** a/src/bin/psql/psqlscanslash.h
--- b/src/bin/psql/psqlscanslash.h
*************** enum slash_option_type
*** 18,25 ****
  	OT_SQLID,					/* treat as SQL identifier */
  	OT_SQLIDHACK,				/* SQL identifier, but don't downcase */
  	OT_FILEPIPE,				/* it's a filename or pipe */
! 	OT_WHOLE_LINE,				/* just snarf the rest of the line */
! 	OT_NO_EVAL					/* no expansion of backticks or variables */
  };
  
  
--- 18,24 ----
  	OT_SQLID,					/* treat as SQL identifier */
  	OT_SQLIDHACK,				/* SQL identifier, but don't downcase */
  	OT_FILEPIPE,				/* it's a filename or pipe */
! 	OT_WHOLE_LINE				/* just snarf the rest of the line */
  };
  
  
*************** extern char *psql_scan_slash_option(Psql
*** 32,37 ****
--- 31,40 ----
  
  extern void psql_scan_slash_command_end(PsqlScanState state);
  
+ extern int	psql_scan_get_paren_depth(PsqlScanState state);
+ 
+ extern void psql_scan_set_paren_depth(PsqlScanState state, int depth);
+ 
  extern void dequote_downcase_identifier(char *str, bool downcase, int encoding);
  
  #endif   /* PSQLSCANSLASH_H */
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index ba4a08d..319afdc 100644
*** a/src/bin/psql/psqlscanslash.l
--- b/src/bin/psql/psqlscanslash.l
***************
*** 19,24 ****
--- 19,25 ----
  #include "postgres_fe.h"
  
  #include "psqlscanslash.h"
+ #include "conditional.h"
  
  #include "libpq-fe.h"
  }
*************** other			.
*** 230,237 ****
  
  :{variable_char}+	{
  					/* Possible psql variable substitution */
! 					if (option_type == OT_NO_EVAL ||
! 						cur_state->callbacks->get_variable == NULL)
  						ECHO;
  					else
  					{
--- 231,237 ----
  
  :{variable_char}+	{
  					/* Possible psql variable substitution */
! 					if (cur_state->callbacks->get_variable == NULL)
  						ECHO;
  					else
  					{
*************** other			.
*** 268,292 ****
  				}
  
  :'{variable_char}+'	{
! 					if (option_type == OT_NO_EVAL)
! 						ECHO;
! 					else
! 					{
! 						psqlscan_escape_variable(cur_state, yytext, yyleng, false);
! 						*option_quote = ':';
! 					}
  					unquoted_option_chars = 0;
  				}
  
  
  :\"{variable_char}+\"	{
! 					if (option_type == OT_NO_EVAL)
! 						ECHO;
! 					else
! 					{
! 						psqlscan_escape_variable(cur_state, yytext, yyleng, true);
! 						*option_quote = ':';
! 					}
  					unquoted_option_chars = 0;
  				}
  
--- 268,282 ----
  				}
  
  :'{variable_char}+'	{
! 					psqlscan_escape_variable(cur_state, yytext, yyleng, false);
! 					*option_quote = ':';
  					unquoted_option_chars = 0;
  				}
  
  
  :\"{variable_char}+\"	{
! 					psqlscan_escape_variable(cur_state, yytext, yyleng, true);
! 					*option_quote = ':';
  					unquoted_option_chars = 0;
  				}
  
*************** other			.
*** 353,360 ****
  	 */
  
  "`"				{
! 					/* In NO_EVAL mode, don't evaluate the command */
! 					if (option_type != OT_NO_EVAL)
  						evaluate_backtick(cur_state);
  					BEGIN(xslasharg);
  				}
--- 343,351 ----
  	 */
  
  "`"				{
! 					/* In an inactive \if branch, don't evaluate the command */
! 					if (cur_state->cb_passthrough == NULL ||
! 						conditional_active((ConditionalStack) cur_state->cb_passthrough))
  						evaluate_backtick(cur_state);
  					BEGIN(xslasharg);
  				}
*************** psql_scan_slash_command_end(PsqlScanStat
*** 642,647 ****
--- 633,657 ----
  }
  
  /*
+  * Fetch current paren nesting depth
+  */
+ int
+ psql_scan_get_paren_depth(PsqlScanState state)
+ {
+ 	return state->paren_depth;
+ }
+ 
+ /*
+  * Set paren nesting depth
+  */
+ void
+ psql_scan_set_paren_depth(PsqlScanState state, int depth)
+ {
+ 	Assert(depth >= 0);
+ 	state->paren_depth = depth;
+ }
+ 
+ /*
   * De-quote and optionally downcase a SQL identifier.
   *
   * The string at *str is modified in-place; it can become shorter,
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 694f0ef..8068a28 100644
*** a/src/bin/psql/startup.c
--- b/src/bin/psql/startup.c
*************** main(int argc, char *argv[])
*** 331,336 ****
--- 331,337 ----
  			else if (cell->action == ACT_SINGLE_SLASH)
  			{
  				PsqlScanState scan_state;
+ 				ConditionalStack cond_stack;
  
  				if (pset.echo == PSQL_ECHO_ALL)
  					puts(cell->val);
*************** main(int argc, char *argv[])
*** 339,349 ****
  				psql_scan_setup(scan_state,
  								cell->val, strlen(cell->val),
  								pset.encoding, standard_strings());
  
! 				successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
  					? EXIT_SUCCESS : EXIT_FAILURE;
  
  				psql_scan_destroy(scan_state);
  			}
  			else if (cell->action == ACT_FILE)
  			{
--- 340,356 ----
  				psql_scan_setup(scan_state,
  								cell->val, strlen(cell->val),
  								pset.encoding, standard_strings());
+ 				cond_stack = conditional_stack_create();
+ 				psql_scan_set_passthrough(scan_state, (void *) cond_stack);
  
! 				successResult = HandleSlashCmds(scan_state,
! 												cond_stack,
! 												NULL,
! 												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..50251c3 100644
*** a/src/test/regress/expected/psql.out
--- b/src/test/regress/expected/psql.out
*************** deallocate q;
*** 2735,2740 ****
--- 2735,2907 ----
  \pset format aligned
  \pset expanded off
  \pset border 1
+ -- tests for \if ... \endif
+ \if true
+   select 'okay';
+  ?column? 
+ ----------
+  okay
+ (1 row)
+ 
+   select 'still okay';
+   ?column?  
+ ------------
+  still okay
+ (1 row)
+ 
+ \else
+   not okay;
+   still not okay
+ \endif
+ -- at this point query buffer should still have last valid line
+ \g
+   ?column?  
+ ------------
+  still okay
+ (1 row)
+ 
+ -- \if should work okay on part of a query
+ select
+   \if true
+     42
+   \else
+     (bogus
+   \endif
+   forty_two;
+  forty_two 
+ -----------
+         42
+ (1 row)
+ 
+ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
+  forty_two 
+ -----------
+         42
+ (1 row)
+ 
+ -- 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 expression": 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 vars and backticks are not expanded when ignoring extra args
+ \set foo bar
+ \echo :foo :'foo' :"foo"
+ bar 'bar' "bar"
+ \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ \pset: extra argument "nosuchcommand" ignored
+ \pset: extra argument ":foo" ignored
+ \pset: extra argument ":'foo'" ignored
+ \pset: extra argument ":"foo"" ignored
+ -- show that vars and backticks are not expanded and commands are ignored
+ -- when in a false if-branch
+ \set try_to_quit '\\q'
+ \if false
+ 	:try_to_quit
+ 	\echo `nosuchcommand` :foo :'foo' :"foo"
+ 	\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ 	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ 	\copy arg1 arg2 arg3 arg4 arg5 arg6
+ 	\copyright \dt arg1 \e arg1 arg2
+ 	\ef whole_line
+ 	\ev whole_line
+ 	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+ 	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+ 	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+ 	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ 	\sf whole_line
+ 	\sv whole_line
+ 	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ 	-- \endif here is eaten as part of whole-line argument
+ 	\! whole_line \endif
+ \else
+ 	\echo 'should print #8-1'
+ should print #8-1
+ \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..d9ab508 100644
*** a/src/test/regress/sql/psql.sql
--- b/src/test/regress/sql/psql.sql
*************** deallocate q;
*** 382,387 ****
--- 382,529 ----
  \pset expanded off
  \pset border 1
  
+ -- tests for \if ... \endif
+ 
+ \if true
+   select 'okay';
+   select 'still okay';
+ \else
+   not okay;
+   still not okay
+ \endif
+ 
+ -- at this point query buffer should still have last valid line
+ \g
+ 
+ -- \if should work okay on part of a query
+ select
+   \if true
+     42
+   \else
+     (bogus
+   \endif
+   forty_two;
+ 
+ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
+ 
+ -- 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 vars and backticks are not expanded when ignoring extra args
+ \set foo bar
+ \echo :foo :'foo' :"foo"
+ \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ 
+ -- show that vars and backticks are not expanded and commands are ignored
+ -- when in a false if-branch
+ \set try_to_quit '\\q'
+ \if false
+ 	:try_to_quit
+ 	\echo `nosuchcommand` :foo :'foo' :"foo"
+ 	\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
+ 	\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ 	\copy arg1 arg2 arg3 arg4 arg5 arg6
+ 	\copyright \dt arg1 \e arg1 arg2
+ 	\ef whole_line
+ 	\ev whole_line
+ 	\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
+ 	\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
+ 	\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
+ 	\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ 	\sf whole_line
+ 	\sv whole_line
+ 	\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ 	-- \endif here is eaten as part of whole-line argument
+ 	\! whole_line \endif
+ \else
+ 	\echo 'should print #8-1'
+ \endif
+ 
  -- SHOW_CONTEXT
  
  \set SHOW_CONTEXT never

^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-30 07:20  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-30 07:20 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers


Hello Tom,

>> Patch applies cleanly. Make check ok. Feature still works!

Idem for v30.

> [...] Aside from cosmetic changes, I've made it behave reasonably for 
> cases where \if is used on portions of a query, for instance
>
> SELECT
> \if :something
>    var1
> \else
>    var2
> \endif
> FROM table;

This is commendable, but I would not have bothered, although it is more 
cpp-like with it.

A small issue I see is that I was planning to add such an if syntax to 
pgbench (well, at least if I succeed in getting boolean expressions and 
setting variables, which is just a maybe), but this kind of if in the 
middle of expression does not make much sense for a pgbench script where 
"if" must be evaluated at execution time, not parse time.

> which as I mentioned a long time ago is something that people will
> certainly expect to work.

I would not have expected it to work, but indeed other people could. 
Sometimes I try something with pg and it does not work as I hoped. That is 
life.

> I also cleaned up a lot of corner-case discrepancies between how much 
> text is consumed in active-branch and inactive-branch cases (OT_FILEPIPE 
> is a particularly nasty case in that regard :-()

Indeed.

> I plan to read this over again tomorrow and then push it, if there are
> not objections/corrections.

My small objection is that an eventual if in pgbench, with a separate 
parsing and execution, will not work in the middle of queries as this one. 
Do you think that such a discrepancy would be admissible.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-30 15:45  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 2 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-30 15:45 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers

Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> [...] Aside from cosmetic changes, I've made it behave reasonably for 
>> cases where \if is used on portions of a query, for instance

> A small issue I see is that I was planning to add such an if syntax to 
> pgbench (well, at least if I succeed in getting boolean expressions and 
> setting variables, which is just a maybe), but this kind of if in the 
> middle of expression does not make much sense for a pgbench script where 
> "if" must be evaluated at execution time, not parse time.

Well, it's not really clear to me why that would be true.  If it actually
is impossible to give pgbench equivalent behavior, we'll just have to live
with the discrepancy, but ISTM it could probably be made to work.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-30 16:22  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-03-30 16:22 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers


Hello Tom,

>> pgbench (well, at least if I succeed in getting boolean expressions and
>> setting variables, which is just a maybe), but this kind of if in the
>> middle of expression does not make much sense for a pgbench script where
>> "if" must be evaluated at execution time, not parse time.
>
> Well, it's not really clear to me why that would be true.

For example, how can you PREPARE a possibly combinatorial thing?

SELECT
   \if ... XX \else YY \endif
FROM
   \if ... ZZ \else WW \endif
WHERE
   \if ... AA \else BB \endif
  ;

Or the kind of operation:

   \if ...
     SELECT *
   \else
     DELETE
   \endif
       FROM table WHERE condition;

Even the structure can be changed somehow:

   SELECT
     \if ...
       1 ;
       SELECT 2
     \endif
       ;

> If it actually is impossible to give pgbench equivalent behavior, we'll 
> just have to live with the discrepancy,

Yep.

> but ISTM it could probably be made to work.

Even if it could somehow, I do not see it as a useful feature for pgbench. 
I also lack a good use case for psql for this feature.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-03-30 16:54  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 232+ messages in thread

From: Tom Lane @ 2017-03-30 16:54 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Daniel Verite <daniel@manitou-mail.org>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers

Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> If it actually is impossible to give pgbench equivalent behavior, we'll 
>> just have to live with the discrepancy,

> Yep.

>> but ISTM it could probably be made to work.

> Even if it could somehow, I do not see it as a useful feature for pgbench. 

Perhaps not.

> I also lack a good use case for psql for this feature.

It doesn't seem very outlandish to me: the alternative would be to repeat
all of a query that might span dozens of lines, in order to change one or
two lines.  That's not very readable or maintainable.

I'm prepared to believe that extremely long queries aren't ever going
to be common in pgbench scripts, so that there's not much need to support
the equivalent behavior in pgbench.  So maybe it's fine.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-04-03 19:32  Daniel Verite <daniel@manitou-mail.org>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 232+ messages in thread

From: Daniel Verite @ 2017-04-03 19:32 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Robert Haas <robertmhaas@gmail.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

  Hi,

In interactive mode, the warning in untaken branches is misleading
when \endif is on the same line as the commands that
are skipped. For instance:

  postgres=# \if false \echo NOK \endif
  \echo command ignored; use \endif or Ctrl-C to exit current \if block
  postgres=# 

From the point of view of the user, the execution flow has exited
the branch already when this warning is displayed.
Of course issuing the recommended \endif at this point doesn't work:

  postgres=# \endif
  \endif: no matching \if

Maybe that part of the message: 
"use \endif or Ctrl-C to exit current \if block"
should be displayed only when coming back at the prompt,
and if the flow is still in an untaken branch at this point?

Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-04-13 20:29  Robert Haas <robertmhaas@gmail.com>
  parent: Daniel Verite <daniel@manitou-mail.org>
  0 siblings, 1 reply; 232+ messages in thread

From: Robert Haas @ 2017-04-13 20:29 UTC (permalink / raw)
  To: Daniel Verite <daniel@manitou-mail.org>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Corey Huinker <corey.huinker@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org

On Mon, Apr 3, 2017 at 3:32 PM, Daniel Verite <daniel@manitou-mail.org> wrote:
> In interactive mode, the warning in untaken branches is misleading
> when \endif is on the same line as the commands that
> are skipped. For instance:
>
>   postgres=# \if false \echo NOK \endif
>   \echo command ignored; use \endif or Ctrl-C to exit current \if block
>   postgres=#
>
> From the point of view of the user, the execution flow has exited
> the branch already when this warning is displayed.
> Of course issuing the recommended \endif at this point doesn't work:
>
>   postgres=# \endif
>   \endif: no matching \if
>
> Maybe that part of the message:
> "use \endif or Ctrl-C to exit current \if block"
> should be displayed only when coming back at the prompt,
> and if the flow is still in an untaken branch at this point?

Is this an open item, or do we not care about fixing it?

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread

* Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless)
@ 2017-04-14 00:08  Fabien COELHO <coelho@cri.ensmp.fr>
  parent: Robert Haas <robertmhaas@gmail.com>
  0 siblings, 0 replies; 232+ messages in thread

From: Fabien COELHO @ 2017-04-14 00:08 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Daniel Verite <daniel@manitou-mail.org>; Tom Lane <tgl@sss.pgh.pa.us>; Corey Huinker <corey.huinker@gmail.com>; David G. Johnston <david.g.johnston@gmail.com>; Greg Stark <stark@mit.edu>; Erik Rijkers <er@xs4all.nl>; Jim Nasby <Jim.Nasby@bluetreble.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org


> On Mon, Apr 3, 2017 at 3:32 PM, Daniel Verite <daniel@manitou-mail.org> wrote:
>> In interactive mode, the warning in untaken branches is misleading
>> when \endif is on the same line as the commands that
>> are skipped. For instance:
>>
>>   postgres=# \if false \echo NOK \endif
>>   \echo command ignored; use \endif or Ctrl-C to exit current \if block
>>   postgres=#
>>
>> From the point of view of the user, the execution flow has exited
>> the branch already when this warning is displayed.
>> Of course issuing the recommended \endif at this point doesn't work:
>>
>>   postgres=# \endif
>>   \endif: no matching \if
>>
>> Maybe that part of the message:
>> "use \endif or Ctrl-C to exit current \if block"
>> should be displayed only when coming back at the prompt,
>> and if the flow is still in an untaken branch at this point?
>
> Is this an open item, or do we not care about fixing it?

I would suggest that this is not important enough to block anything.

Otherwise, I agree that displaying this interactive message only when it 
is pertinent is desirable, but this might change the underlying logic 
significantly: it may mean holding somewhere a message to be shown at next 
prompt, and being able to decide when to clear it.

There is also the question of what happens if there are multiple such 
messages, should they all be shown? Only the first? The last? Should it 
avoid repeats?

So I propose to call it a feature for now, especially that we do not 
expect people to write a lot of one-liner multiple-backslash-commands in 
interactive mode.

-- 
Fabien.


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 232+ messages in thread


end of thread, other threads:[~2017-04-14 00:08 UTC | newest]

Thread overview: 232+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-01-22 23:56 \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless) Corey Huinker <corey.huinker@gmail.com>
2017-01-23 20:37 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-23 20:39   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-23 21:12   ` Corey Huinker <corey.huinker@gmail.com>
2017-01-23 23:03     ` Corey Huinker <corey.huinker@gmail.com>
2017-01-24 06:06       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-24 06:22         ` Corey Huinker <corey.huinker@gmail.com>
2017-01-24 06:15     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-24 07:54       ` Corey Huinker <corey.huinker@gmail.com>
2017-01-24 08:58         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-24 19:52           ` Corey Huinker <corey.huinker@gmail.com>
2017-01-26 20:15             ` Daniel Verite <daniel@manitou-mail.org>
2017-01-26 20:55               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-26 21:06                 ` Corey Huinker <corey.huinker@gmail.com>
2017-01-27 22:43                   ` Corey Huinker <corey.huinker@gmail.com>
2017-01-28 07:38                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-28 17:46                       ` Corey Huinker <corey.huinker@gmail.com>
2017-01-29 08:35                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-29 20:40                           ` Corey Huinker <corey.huinker@gmail.com>
2017-01-29 21:49                           ` Jim Nasby <Jim.Nasby@BlueTreble.com>
2017-01-31 06:04                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-31 17:16                             ` Corey Huinker <corey.huinker@gmail.com>
2017-01-31 18:15                               ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-01 08:27                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 09:48                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-01 13:18                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 13:33                                       ` Fabien COELHO <fabien.coelho@mines-paristech.fr>
2017-02-01 15:33                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-01 13:48                                       ` Erik Rijkers <er@xs4all.nl>
2017-02-01 13:53                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 18:09                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 14:28                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-01 11:31                                   ` Erik Rijkers <er@xs4all.nl>
2017-02-01 13:03                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 13:25                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-01 17:58                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-01 20:03                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-01 21:58                                             ` Jim Nasby <Jim.Nasby@BlueTreble.com>
2017-02-02 22:39                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-02 23:29                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 01:32                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-03 02:06                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 05:57                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-03 07:16                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 11:00                                                           ` Erik Rijkers <er@xs4all.nl>
2017-02-03 12:24                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-03 13:24                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-03 16:08                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 16:38                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-03 16:49                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 17:20                                                                     ` Daniel Verite <daniel@manitou-mail.org>
2017-02-03 20:03                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-04 11:37                                                                         ` Daniel Verite <daniel@manitou-mail.org>
2017-02-04 16:53                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-04 21:00                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-02-05 07:55                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-06 06:39                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-06 09:36                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-06 09:38                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-06 16:21                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-06 18:46                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-06 19:32                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-06 19:50                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-06 20:27                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-06 20:43                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-06 22:49                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-07 07:36                                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-07 18:49                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-07 21:06                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-09 19:58                                                                                                   ` Robert Haas <robertmhaas@gmail.com>
2017-02-09 20:13                                                                                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-09 21:03                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-09 21:15                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-09 21:38                                                                                                         ` Robert Haas <robertmhaas@gmail.com>
2017-02-09 21:43                                                                                                         ` Erik Rijkers <er@xs4all.nl>
2017-02-10 05:13                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-10 07:48                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-10 18:41                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-10 21:36                                                                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-10 22:09                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-02-11 07:43                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-11 20:07                                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-11 20:48                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-11 22:08                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 16:29                                                                                                                       ` Robert Haas <robertmhaas@gmail.com>
2017-02-13 20:40                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-13 21:12                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-14 07:40                                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-14 09:33                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-14 10:22                                                                                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-14 18:26                                                                                                                           ` Robert Haas <robertmhaas@gmail.com>
2017-02-13 21:05                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-11 22:57                                                                                                                   ` Greg Stark <stark@mit.edu>
2017-02-11 23:45                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-12 00:15                                                                                                                       ` Greg Stark <stark@mit.edu>
2017-02-12 08:07                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-13 07:55                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 12:01                                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-13 16:26                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 17:47                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 19:29                                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 20:04                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-13 20:18                                                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-13 21:06                                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-14 21:44                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-14 23:01                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-14 23:03                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-22 15:20                                                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-02-22 21:00                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-22 21:19                                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-22 21:45                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-22 22:11                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-02-22 22:52                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-02-22 22:59                                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-22 23:15                                                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-02-23 05:11                                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-02-23 06:43                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-23 15:05                                                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-02-23 21:46                                                                                                                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-24 00:06                                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-02-24 06:53                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-26 01:54                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-02-26 07:47                                                                                                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-26 20:07                                                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-03-01 04:02                                                                                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-03-01 08:07                                                                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-01 16:34                                                                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-03-01 17:23                                                                                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-01 18:01                                                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-03-02 06:23                                                                                                                                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-02 06:54                                                                                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-03-02 10:04                                                                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-02 19:47                                                                                                                                                                                       ` Corey Huinker <corey.huinker@gmail.com>
2017-03-03 06:19                                                                                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-03 06:25                                                                                                                                                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-03 06:53                                                                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-03-03 08:18                                                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-12 02:09                                                                                                                                                                                                 ` Robert Haas <robertmhaas@gmail.com>
2017-03-12 02:40                                                                                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-12 03:18                                                                                                                                                                                                   ` Robert Haas <robertmhaas@gmail.com>
2017-03-11 21:17                                                                                                                                                                                             ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-11 23:34                                                                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-03-12 07:35                                                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-12 00:45                                                                                                                                                                                             ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-12 06:52                                                                                                                                                                                               ` David G. Johnston <david.g.johnston@gmail.com>
2017-03-12 17:12                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-12 17:24                                                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-12 17:45                                                                                                                                                                                                     ` David G. Johnston <david.g.johnston@gmail.com>
2017-03-12 19:29                                                                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-13 19:11                                                                                                                                                                                                         ` Daniel Verite <daniel@manitou-mail.org>
2017-03-13 21:21                                                                                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-16 20:57                                                                                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-03-16 21:14                                                                                                                                                                                                               ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-17 01:28                                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-17 06:18                                                                                                                                                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-17 14:37                                                                                                                                                                                                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-17 15:32                                                                                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-17 15:42                                                                                                                                                                                                                         ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-17 16:03                                                                                                                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-03-17 16:33                                                                                                                                                                                                                             ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-17 16:56                                                                                                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-03-17 17:27                                                                                                                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-17 18:18                                                                                                                                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-03-18 17:13                                                                                                                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-03-19 17:18                                                                                                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-19 17:26                                                                                                                                                                                                                                         ` Alvaro Herrera <alvherre@2ndquadrant.com>
2017-03-19 17:48                                                                                                                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-19 20:00                                                                                                                                                                                                                                             ` Alvaro Herrera <alvherre@2ndquadrant.com>
2017-03-19 20:23                                                                                                                                                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-19 21:05                                                                                                                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-03-19 21:18                                                                                                                                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-03-24 16:52                                                                                                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-24 19:16                                                                                                                                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-03-24 20:10                                                                                                                                                                                                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-24 20:33                                                                                                                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-03-24 22:53                                                                                                                                                                                                                                               ` Corey Huinker <corey.huinker@gmail.com>
2017-03-25 06:17                                                                                                                                                                                                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-25 06:21                                                                                                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-25 15:33                                                                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-26 10:32                                                                                                                                                                                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-26 21:42                                                                                                                                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-03-27 13:53                                                                                                                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-27 14:11                                                                                                                                                                                                                                                         ` Corey Huinker <corey.huinker@gmail.com>
2017-03-27 14:34                                                                                                                                                                                                                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-27 15:39                                                                                                                                                                                                                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-03-27 19:25                                                                                                                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-27 19:51                                                                                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-27 19:53                                                                                                                                                                                                                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-03-27 21:34                                                                                                                                                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-29 00:52                                                                                                                                                                                                                                                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-29 04:18                                                                                                                                                                                                                                                                       ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-29 14:14                                                                                                                                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-29 18:16                                                                                                                                                                                                                                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-29 19:34                                                                                                                                                                                                                                                                           ` Corey Huinker <corey.huinker@gmail.com>
2017-03-29 20:51                                                                                                                                                                                                                                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-30 04:15                                                                                                                                                                                                                                                                               ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-30 07:20                                                                                                                                                                                                                                                                                 ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-30 15:45                                                                                                                                                                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-30 16:22                                                                                                                                                                                                                                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-30 16:54                                                                                                                                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-04-03 19:32                                                                                                                                                                                                                                                                                 ` Daniel Verite <daniel@manitou-mail.org>
2017-04-13 20:29                                                                                                                                                                                                                                                                                   ` Robert Haas <robertmhaas@gmail.com>
2017-04-14 00:08                                                                                                                                                                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-17 16:42                                                                                                                                                                                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-17 10:03                                                                                                                                                                                                                   ` Erik Rijkers <er@xs4all.nl>
2017-03-17 15:40                                                                                                                                                                                                                     ` Corey Huinker <corey.huinker@gmail.com>
2017-03-17 11:33                                                                                                                                                                                                                 ` Daniel Verite <daniel@manitou-mail.org>
2017-03-17 13:33                                                                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-12 08:22                                                                                                                                                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-03-12 17:40                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-13 15:18                                                                                                                                                                                               ` Tom Lane <tgl@sss.pgh.pa.us>
2017-03-13 16:36                                                                                                                                                                                                 ` Corey Huinker <corey.huinker@gmail.com>
2017-03-13 21:15                                                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-23 15:28                                                                                                                                                   ` Daniel Verite <daniel@manitou-mail.org>
2017-02-23 17:52                                                                                                                                                     ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-23 19:55                                                                                                                                                       ` Pavel Stehule <pavel.stehule@gmail.com>
2017-02-03 17:10                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-03 20:49                                                                 ` Robert Haas <robertmhaas@gmail.com>
2017-02-03 21:24                                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-02-03 21:37                                                                     ` Robert Haas <robertmhaas@gmail.com>
2017-02-04 07:26                                                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-04 16:47                                                             ` Corey Huinker <corey.huinker@gmail.com>
2017-02-04 00:42                                                 ` Jim Nasby <Jim.Nasby@BlueTreble.com>
2017-02-04 01:21                                                   ` Corey Huinker <corey.huinker@gmail.com>
2017-01-30 16:57                         ` Daniel Verite <daniel@manitou-mail.org>
2017-01-30 17:35                           ` Fabien COELHO <coelho@cri.ensmp.fr>
2017-01-24 18:08     ` Daniel Verite <daniel@manitou-mail.org>
2017-01-24 18:25       ` Corey Huinker <corey.huinker@gmail.com>
2017-01-24 18:40         ` Corey Huinker <corey.huinker@gmail.com>
2017-01-24 19:38       ` Tom Lane <tgl@sss.pgh.pa.us>
2017-02-01 14:00 Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless) Fabien COELHO <coelho@cri.ensmp.fr>
2017-02-01 18:12 ` Corey Huinker <corey.huinker@gmail.com>

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox