Fabien COELHO <coe...@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

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
-- 
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