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 <coe...@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.
>
From 9caa338fa085bc1c0ee16f22c0c1c8d3c8fc1697 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huin...@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

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

Reply via email to