diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..7743fb0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,102 @@ hello 10
+ \ifexpression
+ \elifexpression
+ \else
+ \endif
+
+
+ This group of commands implements nestable conditional blocks.
+ A conditional block must begin with an \if and end
+ with an \endif. In between there may be any number
+ of \elif clauses, which may optionally be followed
+ by a single \else clause. Ordinary queries and
+ other types of backslash commands may (and usually do) appear between
+ the commands forming a conditional block.
+
+
+ The \if and \elif commands read
+ the rest of the line and evaluate it as a boolean expression. If the
+ expression is true then processing continues
+ normally; otherwise, lines are skipped until a
+ matching \elif, \else,
+ or \endif is reached. Once
+ an \if or \elif has succeeded,
+ later matching \elif commands are not evaluated but
+ are treated as false. Lines following an \else are
+ processed only if no earlier matching \if
+ or \elif succeeded.
+
+
+ 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
+ (\if, \elif,
+ \else, \endif) are
+ ignored. Conditional commands are checked only for valid nesting.
+
+
+ The expression argument
+ of \if or \elif
+ 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:
+ true, false, 1,
+ 0, on, off,
+ yes, no. For example,
+ t, T, and tR
+ will all be considered to be true.
+
+
+ Expressions that do not properly evaluate to true or false will
+ generate an error and cause the \if or
+ \elif command to fail. Because that behavior may
+ change branching context in undesirable ways (executing code which
+ was intended to be skipped, causing \elif,
+ \else, and \endif commands to
+ pair with the wrong \if, etc), it is
+ recommended that scripts that use conditionals also set
+ ON_ERROR_STOP.
+
+
+ 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
+ \include-ed file before all local
+ \if-blocks have been closed,
+ then psql> will raise an error.
+
+
+ Here is an example:
+
+
+-- set ON_ERROR_STOP in case the variables are not valid boolean expressions
+\set ON_ERROR_STOP on
+-- check for the existence of two separate records in the database and store
+-- the results in separate psql variables
+SELECT
+ EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
+ EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
+\gset
+\if :is_customer
+ SELECT * FROM customer WHERE customer_id = 123;
+\elif :is_employee
+ \echo 'is not a customer but is an employee'
+ SELECT * FROM employee WHERE employee_id = 456;
+\else
+ \if yes
+ \echo 'not a customer or employee'
+ \else
+ \echo 'this will never print'
+ \endif
+\endif
+
+
+
+
+
+ \l[+] or \list[+] [ pattern ]
@@ -3715,7 +3811,8 @@ testdb=> INSERT INTO my_table VALUES (:'content');
In prompt 1 normally =,
- but ^ if in single-line mode,
+ but @ if the session is in a false conditional
+ block, or ^ if in single-line mode,
or ! if the session is disconnected from the
database (which can happen if \connect fails).
In prompt 2 %R is replaced by a character that
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b1..5239013 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,5 @@
/sql_help.c
/psql
+
+/tmp_check/
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f8e31ea..90ee85d 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
-OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
+OBJS= command.o common.o conditional.o copy.o help.o input.o mainloop.o \
startup.o prompt.o variables.o large_obj.o describe.o \
crosstabview.o tab-complete.o \
- sql_help.o psqlscanslash.o \
+ sql_help.o stringutils.o psqlscanslash.o \
$(WIN32RES)
@@ -57,8 +57,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4f4a0aa..4bdac08 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -35,6 +35,7 @@
#include "fe_utils/string_utils.h"
#include "common.h"
+#include "conditional.h"
#include "copy.h"
#include "crosstabview.h"
#include "describe.h"
@@ -42,6 +43,7 @@
#include "input.h"
#include "large_obj.h"
#include "mainloop.h"
+#include "fe_utils/psqlscan_int.h"
#include "fe_utils/print.h"
#include "psqlscanslash.h"
#include "settings.h"
@@ -85,6 +87,12 @@ static void checkWin32Codepage(void);
#endif
+static ConditionalStack
+get_conditional_stack(PsqlScanState scan_state)
+{
+ return (ConditionalStack) scan_state->cb_passthrough;
+}
+
/*----------
* HandleSlashCmds:
@@ -111,8 +119,10 @@ HandleSlashCmds(PsqlScanState scan_state,
backslashResult status = PSQL_CMD_SKIP_LINE;
char *cmd;
char *arg;
+ ConditionalStack cstack = get_conditional_stack(scan_state);
Assert(scan_state != NULL);
+ Assert(cstack != NULL);
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
@@ -129,7 +139,7 @@ HandleSlashCmds(PsqlScanState scan_state,
status = PSQL_CMD_ERROR;
}
- if (status != PSQL_CMD_ERROR)
+ if (status != PSQL_CMD_ERROR && conditional_active(cstack))
{
/* eat any remaining arguments after a valid command */
/* note we suppress evaluation of backticks here */
@@ -191,6 +201,94 @@ read_connect_arg(PsqlScanState scan_state)
return result;
}
+/*
+ * Read a boolean expression.
+ * Expand variables/backticks if expansion is true.
+ * Issue warning for nonstandard number of options is warn is true.
+ */
+static char*
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+ enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+ char *expression_buffer = NULL;
+ int num_options = 0;
+ char *value;
+
+ /* digest all options for the conditional command */
+ while ((value = psql_scan_slash_option(scan_state, slash_opt, NULL, false)))
+ {
+ num_options++;
+
+ if (expression_buffer)
+ {
+ char *old_expr_buf = expression_buffer;
+ expression_buffer = psprintf("%s %s", old_expr_buf, value);
+ free(old_expr_buf);
+ free(value);
+ }
+ else
+ expression_buffer = value;
+ }
+
+ /* currently, we expect exactly one option */
+ if (warn)
+ {
+ if (num_options == 0)
+ psql_error("WARNING: Boolean expression with no options");
+ else if (num_options > 1)
+ psql_error("WARNING: Boolean expression with %d options: %s\n",
+ num_options, expression_buffer);
+ }
+
+ return expression_buffer;
+}
+
+/*
+ * Read a boolean expression, but do nothing with it.
+ */
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
+{
+ free(gather_boolean_expression(scan_state, false, false));
+}
+
+/*
+ * Read and interpret argument as a boolean expression.
+ * Return true if a boolean value was successfully parsed.
+ * Do not clobber result if parse was not successful.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, char *action,
+ bool expansion, bool *result)
+{
+ char *expr = gather_boolean_expression(scan_state, true, true);
+ bool success = ParseVariableBool(expr, action, result);
+ free(expr);
+ return success;
+}
+
+/*
+ * Read a boolean expression, return true if the expression
+ * was a valid boolean expression that evaluated to true.
+ * Otherwise return false.
+ */
+static bool
+is_true_boolean_expression(PsqlScanState scan_state, char *action)
+{
+ bool tf;
+ bool success = read_boolean_expression(scan_state, action, true, &tf);
+ return (success) ? tf : false;
+}
+
+/*
+ * Return true if the command given is a branching command.
+ */
+static bool
+is_branching_command(const char *cmd)
+{
+ return (strcmp(cmd, "if") == 0 || strcmp(cmd, "elif") == 0
+ || strcmp(cmd, "else") == 0 || strcmp(cmd, "endif") == 0);
+}
/*
* Subroutine to actually try to execute a backslash command.
@@ -200,10 +298,27 @@ exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf)
{
+ ConditionalStack cstack = get_conditional_stack(scan_state);
bool success = true; /* indicate here if the command ran ok or
* failed */
backslashResult status = PSQL_CMD_SKIP_LINE;
+ if (!conditional_active(cstack) && !is_branching_command(cmd))
+ {
+ char *arg;
+
+ if (pset.cur_cmd_interactive)
+ psql_error("command ignored, use \\endif or Ctrl-C to exit "
+ "current branch.\n");
+
+ /* Digest and ignore any options on this command */
+ while ((arg = psql_scan_slash_option(scan_state,
+ OT_NO_EVAL, NULL, false)))
+ free(arg);
+
+ return status;
+ }
+
/*
* \a -- toggle field alignment This makes little sense but we keep it
* around.
@@ -1008,6 +1123,143 @@ exec_command(const char *cmd,
}
}
+ /*
+ * \if is the beginning of an \if..\endif block. must be a
+ * valid boolean expression, or the command will be ignored. If this \if
+ * is itself a part of a branch that is false/ignored, the expression
+ * will be checked for validity but cannot override the outer block.
+ */
+ else if (strcmp(cmd, "if") == 0)
+ {
+ switch (conditional_stack_peek(cstack))
+ {
+ case IFSTATE_IGNORED:
+ case IFSTATE_FALSE:
+ case IFSTATE_ELSE_FALSE:
+ /* new if-block, expression should not be evaluated,
+ * but call it in silent mode to digest options */
+ ignore_boolean_expression(scan_state);
+ conditional_stack_push(cstack, IFSTATE_IGNORED);
+ break;
+ default:
+ /*
+ * new if-block, check expression for truth.
+ */
+ if (is_true_boolean_expression(scan_state, "\\if "))
+ conditional_stack_push(cstack, IFSTATE_TRUE);
+ else
+ conditional_stack_push(cstack, IFSTATE_FALSE);
+ break;
+ }
+ }
+
+ /*
+ * \elif is part of an \if..\endif block. must be a valid
+ * boolean expression, or the command will be ignored.
+ */
+ else if (strcmp(cmd, "elif") == 0)
+ {
+ switch (conditional_stack_peek(cstack))
+ {
+ case IFSTATE_IGNORED:
+ /*
+ * inactive branch, digest expression and move on.
+ * either if-endif already had a true block,
+ * or whole parent block is false.
+ */
+ ignore_boolean_expression(scan_state);
+ break;
+ case IFSTATE_TRUE:
+ /*
+ * just finished true section of this if-endif, digest
+ * expression, but then ignore the rest until \endif
+ */
+ ignore_boolean_expression(scan_state);
+ conditional_stack_poke(cstack, IFSTATE_IGNORED);
+ break;
+ case IFSTATE_FALSE:
+ /*
+ * have not yet found a true block in this if-endif,
+ * this might be the first.
+ */
+ if (is_true_boolean_expression(scan_state, "\\elif "))
+ conditional_stack_poke(cstack, IFSTATE_TRUE);
+ break;
+ case IFSTATE_NONE:
+ /* no if to elif from */
+ psql_error("\\elif: no matching \\if\n");
+ success = false;
+ break;
+ case IFSTATE_ELSE_TRUE:
+ case IFSTATE_ELSE_FALSE:
+ psql_error("\\elif: cannot occur after \\else\n");
+ success = false;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * \else is part of an \if..\endif block
+ * the statements within an \else branch will only be executed if
+ * all previous \if and \endif expressions evaluated to false
+ * and the block was not itself being ignored.
+ */
+ else if (strcmp(cmd, "else") == 0)
+ {
+ switch (conditional_stack_peek(cstack))
+ {
+ case IFSTATE_FALSE:
+ /* just finished false section of an active branch */
+ conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE);
+ break;
+ case IFSTATE_TRUE:
+ case IFSTATE_IGNORED:
+ /*
+ * either just finished true section of an active branch,
+ * or whole branch was inactive. either way, be on the
+ * lookout for any invalid \endif or \else commands
+ */
+ conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
+ break;
+ case IFSTATE_NONE:
+ /* no if to else from */
+ psql_error("\\else: no matching \\if\n");
+ success = false;
+ break;
+ case IFSTATE_ELSE_TRUE:
+ case IFSTATE_ELSE_FALSE:
+ psql_error("\\else: cannot occur after \\else\n");
+ success = false;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * \endif - closing statment of an \if...\endif block
+ */
+ else if (strcmp(cmd, "endif") == 0)
+ {
+ /*
+ * get rid of this ifstate element and look at the previous
+ * one, if any
+ */
+ switch (conditional_stack_peek(cstack))
+ {
+ case IFSTATE_NONE:
+ psql_error("\\endif: no matching \\if\n");
+ success = false;
+ break;
+ default:
+ success = conditional_stack_pop(cstack);
+ Assert(success);
+ break;
+ }
+ }
+
/* \l is list databases */
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index e9d4fe6..e2299f4 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -24,6 +24,7 @@
#include "settings.h"
#include "command.h"
+#include "conditional.h"
#include "copy.h"
#include "crosstabview.h"
#include "fe_utils/mbprint.h"
@@ -121,7 +122,8 @@ setQFout(const char *fname)
* (Failure in escaping should lead to returning NULL.)
*
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
- * psql currently doesn't use this.
+ * Currently, passthrough points to a ConditionalStack, but in the future
+ * it may point to a structure with additional state information.
*/
char *
psql_get_variable(const char *varname, bool escape, bool as_ident,
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 481031a..2005b9a 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -552,7 +552,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
/* interactive input probably silly, but give one prompt anyway */
if (showprompt)
{
- const char *prompt = get_prompt(PROMPT_COPY);
+ const char *prompt = get_prompt(PROMPT_COPY, NULL);
fputs(prompt, stdout);
fflush(stdout);
@@ -590,7 +590,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
if (showprompt)
{
- const char *prompt = get_prompt(PROMPT_COPY);
+ const char *prompt = get_prompt(PROMPT_COPY, NULL);
fputs(prompt, stdout);
fflush(stdout);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ba14df0..79afafb 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n"));
fprintf(output, "\n");
+ fprintf(output, _("Conditionals\n"));
+ fprintf(output, _(" \\if begin a conditional block\n"));
+ fprintf(output, _(" \\elif else if in the current conditional block\n"));
+ fprintf(output, _(" \\else else in the current conditional block\n"));
+ fprintf(output, _(" \\endif end current conditional block\n"));
+ fprintf(output, "\n");
+
fprintf(output, _("Informational\n"));
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
fprintf(output, _(" \\d[S+] list tables, views, and sequences\n"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..e9e1e3f 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -25,6 +25,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static bool
+send_query(const char *query, ConditionalStack cstack)
+{
+ /* execute query if branch is active */
+ if (conditional_active(cstack))
+ return SendQuery(query);
+
+ if (pset.cur_cmd_interactive)
+ psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if branch\n");
+
+ return true;
+}
+
+/*
* Main processing loop for reading lines of input
* and sending them to the backend.
*
@@ -35,6 +52,7 @@ int
MainLoop(FILE *source)
{
PsqlScanState scan_state; /* lexer working state */
+ ConditionalStack cond_stack; /* \if status stack */
volatile PQExpBuffer query_buf; /* buffer for query being accumulated */
volatile PQExpBuffer previous_buf; /* if there isn't anything in the new
* buffer yet, use this one for \e,
@@ -69,6 +87,8 @@ MainLoop(FILE *source)
/* Create working state */
scan_state = psql_scan_create(&psqlscan_callbacks);
+ cond_stack = conditional_stack_create();
+ psql_scan_set_passthrough(scan_state, (void *) cond_stack);
query_buf = createPQExpBuffer();
previous_buf = createPQExpBuffer();
@@ -122,7 +142,18 @@ MainLoop(FILE *source)
cancel_pressed = false;
if (pset.cur_cmd_interactive)
+ {
putc('\n', stdout);
+ /*
+ * if interactive user is in a branch, then Ctrl-C will exit
+ * from the inner-most branch
+ */
+ if (!conditional_stack_empty(cond_stack))
+ {
+ psql_error("\\if: escaped\n");
+ conditional_stack_pop(cond_stack);
+ }
+ }
else
{
successResult = EXIT_USER;
@@ -140,7 +171,8 @@ MainLoop(FILE *source)
/* May need to reset prompt, eg after \r command */
if (query_buf->len == 0)
prompt_status = PROMPT_READY;
- line = gets_interactive(get_prompt(prompt_status), query_buf);
+ line = gets_interactive(get_prompt(prompt_status, cond_stack),
+ query_buf);
}
else
{
@@ -297,7 +329,7 @@ MainLoop(FILE *source)
}
/* execute query */
- success = SendQuery(query_buf->data);
+ success = send_query(query_buf->data, cond_stack);
slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
pset.stmt_lineno = 1;
@@ -358,7 +390,7 @@ MainLoop(FILE *source)
if (slashCmdStatus == PSQL_CMD_SEND)
{
- success = SendQuery(query_buf->data);
+ success = send_query(query_buf->data, cond_stack);
/* transfer query to previous_buf by pointer-swapping */
{
@@ -430,7 +462,7 @@ MainLoop(FILE *source)
pg_send_history(history_buf);
/* execute query */
- success = SendQuery(query_buf->data);
+ success = send_query(query_buf->data, cond_stack);
if (!success && die_on_error)
successResult = EXIT_USER;
@@ -439,6 +471,19 @@ MainLoop(FILE *source)
}
/*
+ * Check for unbalanced \if-\endifs unless user explicitly quit, or the
+ * script is erroring out
+ */
+ if (slashCmdStatus != PSQL_CMD_TERMINATE &&
+ successResult != EXIT_USER &&
+ !conditional_stack_empty(cond_stack))
+ {
+ psql_error("reached EOF without finding closing \\endif(s)\n");
+ if (die_on_error && !pset.cur_cmd_interactive)
+ successResult = EXIT_USER;
+ }
+
+ /*
* Let's just make real sure the SIGINT handler won't try to use
* sigint_interrupt_jmp after we exit this routine. If there is an outer
* MainLoop instance, it will reset sigint_interrupt_jmp to point to
@@ -452,6 +497,7 @@ MainLoop(FILE *source)
destroyPQExpBuffer(history_buf);
psql_scan_destroy(scan_state);
+ conditional_stack_destroy(cond_stack);
pset.cur_cmd_source = prev_cmd_source;
pset.cur_cmd_interactive = prev_cmd_interactive;
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index f7930c4..e502ff3 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -66,7 +66,7 @@
*/
char *
-get_prompt(promptStatus_t status)
+get_prompt(promptStatus_t status, ConditionalStack cstack)
{
#define MAX_PROMPT_SIZE 256
static char destination[MAX_PROMPT_SIZE + 1];
@@ -188,7 +188,9 @@ get_prompt(promptStatus_t status)
switch (status)
{
case PROMPT_READY:
- if (!pset.db)
+ if (cstack != NULL && !conditional_active(cstack))
+ buf[0] = '@';
+ else if (!pset.db)
buf[0] = '!';
else if (!pset.singleline)
buf[0] = '=';
diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h
index 977e754..b3d2d98 100644
--- a/src/bin/psql/prompt.h
+++ b/src/bin/psql/prompt.h
@@ -10,7 +10,8 @@
/* enum promptStatus_t is now defined by psqlscan.h */
#include "fe_utils/psqlscan.h"
+#include "conditional.h"
-char *get_prompt(promptStatus_t status);
+char *get_prompt(promptStatus_t status, ConditionalStack cstack);
#endif /* PROMPT_H */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 694f0ef..6882192 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -18,6 +18,7 @@
#include "command.h"
#include "common.h"
+#include "conditional.h"
#include "describe.h"
#include "help.h"
#include "input.h"
@@ -331,19 +332,23 @@ main(int argc, char *argv[])
else if (cell->action == ACT_SINGLE_SLASH)
{
PsqlScanState scan_state;
+ ConditionalStack cond_stack;
if (pset.echo == PSQL_ECHO_ALL)
puts(cell->val);
scan_state = psql_scan_create(&psqlscan_callbacks);
+ cond_stack = conditional_stack_create();
psql_scan_setup(scan_state,
cell->val, strlen(cell->val),
pset.encoding, standard_strings());
- successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
+ successResult = HandleSlashCmds(scan_state,
+ NULL) != PSQL_CMD_ERROR
? EXIT_SUCCESS : EXIT_FAILURE;
psql_scan_destroy(scan_state);
+ conditional_stack_destroy(cond_stack);
}
else if (cell->action == ACT_FILE)
{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index eb7f197..c9fb1a0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,106 @@ deallocate q;
\pset format aligned
\pset expanded off
\pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+ \if 1
+ \if yes
+ \if on
+ \echo 'all true'
+all true
+ \else
+ \echo 'should not print #1-1'
+ \endif
+ \else
+ \echo 'should not print #1-2'
+ \endif
+ \else
+ \echo 'should not print #1-3'
+ \endif
+\else
+ \echo 'should not print #1-4'
+\endif
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+ \echo 'should not print #2-1'
+\elif 0
+ \echo 'should not print #2-2'
+\elif no
+ \echo 'should not print #2-3'
+\elif off
+ \echo 'should not print #2-4'
+\else
+ \echo 'all false'
+all false
+\endif
+-- test simple true-then-else
+\if true
+ \echo 'first thing true'
+first thing true
+\else
+ \echo 'should not print #3-1'
+\endif
+-- test simple false-true-else
+\if false
+ \echo 'should not print #4-1'
+\elif true
+ \echo 'second thing true'
+second thing true
+\else
+ \echo 'should not print #5-1'
+\endif
+-- invalid boolean expressions are false
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if ": boolean expected
+ \echo 'will not print #6-1'
+\else
+ \echo 'will print anyway #6-2'
+will print anyway #6-2
+\endif
+-- test un-matched endif
+\endif
+\endif: no matching \if
+-- test un-matched else
+\else
+\else: no matching \if
+-- test un-matched elif
+\elif
+\elif: no matching \if
+-- test double-else error
+\if true
+\else
+\else
+\else: cannot occur after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+\elif: cannot occur after \else
+\endif
+-- test if-endif matching in a false branch
+\if false
+ \if false
+ \echo 'should not print #7-1'
+ \else
+ \echo 'should not print #7-2'
+ \endif
+ \echo 'should not print #7-3'
+\else
+ \echo 'should print #7-4'
+should print #7-4
+\endif
+-- show that variables still expand even in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+ select :var;
+-- this will be skipped because of an unterminated string
+\endif
+-- fix the unterminated string
+';
+-- now the if block can be properly ended
+\endif
-- SHOW_CONTEXT
\set SHOW_CONTEXT never
do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f8e17a..c812e97 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,106 @@ deallocate q;
\pset expanded off
\pset border 1
+-- test a large nested if using a variety of true-equivalents
+\if true
+ \if 1
+ \if yes
+ \if on
+ \echo 'all true'
+ \else
+ \echo 'should not print #1-1'
+ \endif
+ \else
+ \echo 'should not print #1-2'
+ \endif
+ \else
+ \echo 'should not print #1-3'
+ \endif
+\else
+ \echo 'should not print #1-4'
+\endif
+
+-- test a variety of false-equivalents in an if/elif/else structure
+\if false
+ \echo 'should not print #2-1'
+\elif 0
+ \echo 'should not print #2-2'
+\elif no
+ \echo 'should not print #2-3'
+\elif off
+ \echo 'should not print #2-4'
+\else
+ \echo 'all false'
+\endif
+
+-- test simple true-then-else
+\if true
+ \echo 'first thing true'
+\else
+ \echo 'should not print #3-1'
+\endif
+
+-- test simple false-true-else
+\if false
+ \echo 'should not print #4-1'
+\elif true
+ \echo 'second thing true'
+\else
+ \echo 'should not print #5-1'
+\endif
+
+-- invalid boolean expressions are false
+\if invalid_boolean_expression
+ \echo 'will not print #6-1'
+\else
+ \echo 'will print anyway #6-2'
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
+-- test if-endif matching in a false branch
+\if false
+ \if false
+ \echo 'should not print #7-1'
+ \else
+ \echo 'should not print #7-2'
+ \endif
+ \echo 'should not print #7-3'
+\else
+ \echo 'should print #7-4'
+\endif
+
+-- show that variables still expand even in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+ select :var;
+-- this will be skipped because of an unterminated string
+\endif
+-- fix the unterminated string
+';
+-- now the if block can be properly ended
+\endif
+
-- SHOW_CONTEXT
\set SHOW_CONTEXT never