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 + \if expression + \elif expression + \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