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