New Patch v29: Now with less coverage! (same as v28 minus the psql-on-error-stop.sql and associated changes) Fabien raises some good points about if/then being a tremendous tool for enhancing other existing regression tests.
On Wed, Mar 29, 2017 at 2:16 PM, Fabien COELHO <coe...@cri.ensmp.fr> wrote: > > Hello Tom, > > If someone were to put together a TAP test suite that covered all that >> and made for a meaningful improvement in psql's altogether-miserable >> code coverage report[1], I would think that that would be a useful >> expenditure of buildfarm time. >> > > Ok, this is an interesting point. > > What I'm objecting to is paying the overhead for such a suite in order to >> test just this one thing. >> > > Well, it should start somewhere. Once something is running it is easier to > add more tests. > > think that that passes the bang-for-buck test; or in other words, this >> isn't the place I would start if I were creating a TAP suite for psql. >> > > Sure, I would not have started with that either. > > Note that from this patch point of view, it is somehow logical to start > testing a given feature when this very feature is being developed... > > The summary is that we agree that psql test coverage is abysmal, but you > do not want to bootstrap a better test infrastructure for this particular > and rather special new feature. Ok. > > Maybe Corey can submit another patch with the exit 3 test removed. > > -- > Fabien. >
From 9caa338fa085bc1c0ee16f22c0c1c8d3c8fc1697 Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@moat.com> Date: Wed, 29 Mar 2017 15:30:22 -0400 Subject: [PATCH] psql if v29 --- doc/src/sgml/ref/psql-ref.sgml | 90 ++- src/bin/psql/.gitignore | 2 + src/bin/psql/Makefile | 4 +- src/bin/psql/command.c | 1365 ++++++++++++++++++++++++++++-------- src/bin/psql/common.c | 4 +- src/bin/psql/conditional.c | 103 +++ src/bin/psql/conditional.h | 62 ++ src/bin/psql/copy.c | 4 +- src/bin/psql/help.c | 7 + src/bin/psql/mainloop.c | 54 +- src/bin/psql/prompt.c | 6 +- src/bin/psql/prompt.h | 3 +- src/bin/psql/startup.c | 8 +- src/test/regress/expected/psql.out | 110 +++ src/test/regress/sql/psql.sql | 109 +++ 15 files changed, 1641 insertions(+), 290 deletions(-) create mode 100644 src/bin/psql/conditional.c create mode 100644 src/bin/psql/conditional.h diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 2a9c412..18f8bfe 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -2064,6 +2064,93 @@ hello 10 <varlistentry> + <term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term> + <term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term> + <term><literal>\else</literal></term> + <term><literal>\endif</literal></term> + <listitem> + <para> + This group of commands implements nestable conditional blocks. + A conditional block must begin with an <command>\if</command> and end + with an <command>\endif</command>. In between there may be any number + of <command>\elif</command> clauses, which may optionally be followed + by a single <command>\else</command> clause. Ordinary queries and + other types of backslash commands may (and usually do) appear between + the commands forming a conditional block. + </para> + <para> + The <command>\if</command> and <command>\elif</command> commands read + the rest of the line and evaluate it as a boolean expression. If the + expression is <literal>true</literal> then processing continues + normally; otherwise, lines are skipped until a + matching <command>\elif</command>, <command>\else</command>, + or <command>\endif</command> is reached. Once + an <command>\if</command> or <command>\elif</command> has succeeded, + later matching <command>\elif</command> commands are not evaluated but + are treated as false. Lines following an <command>\else</command> are + processed only if no earlier matching <command>\if</command> + or <command>\elif</command> succeeded. + </para> + <para> + Lines being skipped are parsed normally to identify queries and + backslash commands, but queries are not sent to the server, and + backslash commands other than conditionals + (<command>\if</command>, <command>\elif</command>, + <command>\else</command>, <command>\endif</command>) are + ignored. Conditional commands are checked only for valid nesting. + </para> + <para> + The <replaceable class="parameter">expression</replaceable> argument + of <command>\if</command> or <command>\elif</command> + is subject to variable interpolation and backquote expansion, just + like any other backslash command argument. After that it is evaluated + like the value of an on/off option variable. So a valid value + is any unambiguous case-insensitive match for one of: + <literal>true</literal>, <literal>false</literal>, <literal>1</literal>, + <literal>0</literal>, <literal>on</literal>, <literal>off</literal>, + <literal>yes</literal>, <literal>no</literal>. For example, + <literal>t</literal>, <literal>T</literal>, and <literal>tR</literal> + will all be considered to be <literal>true</literal>. + </para> + <para> + Expressions that do not properly evaluate to true or false will + generate a warning and be treated as false. + </para> + <para> + All the backslash commands of a given conditional block must appear in + the same source file. If EOF is reached on the main input file or an + <command>\include</command>-ed file before all local + <command>\if</command>-blocks have been closed, + then <application>psql</> will raise an error. + </para> + <para> + Here is an example: + </para> +<programlisting> +-- check for the existence of two separate records in the database and store +-- the results in separate psql variables +SELECT + EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer, + EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee +\gset +\if :is_customer + SELECT * FROM customer WHERE customer_id = 123; +\elif :is_employee + \echo 'is not a customer but is an employee' + SELECT * FROM employee WHERE employee_id = 456; +\else + \if yes + \echo 'not a customer or employee' + \else + \echo 'this will never print' + \endif +\endif +</programlisting> + </listitem> + </varlistentry> + + + <varlistentry> <term><literal>\l[+]</literal> or <literal>\list[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term> <listitem> <para> @@ -3715,7 +3802,8 @@ testdb=> <userinput>INSERT INTO my_table VALUES (:'content');</userinput> <listitem> <para> In prompt 1 normally <literal>=</literal>, - but <literal>^</literal> if in single-line mode, + but <literal>@</literal> if the session is in a false conditional + block, or <literal>^</literal> if in single-line mode, or <literal>!</literal> if the session is disconnected from the database (which can happen if <command>\connect</command> fails). In prompt 2 <literal>%R</literal> is replaced by a character that diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore index c2862b1..5239013 100644 --- a/src/bin/psql/.gitignore +++ b/src/bin/psql/.gitignore @@ -3,3 +3,5 @@ /sql_help.c /psql + +/tmp_check/ diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile index f8e31ea..cfc4972 100644 --- a/src/bin/psql/Makefile +++ b/src/bin/psql/Makefile @@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq -OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \ +OBJS= command.o common.o conditional.o copy.o help.o input.o mainloop.o \ startup.o prompt.o variables.o large_obj.o describe.o \ crosstabview.o tab-complete.o \ - sql_help.o psqlscanslash.o \ + sql_help.o stringutils.o psqlscanslash.o \ $(WIN32RES) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 4f4a0aa..98817eb 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -35,6 +35,7 @@ #include "fe_utils/string_utils.h" #include "common.h" +#include "conditional.h" #include "copy.h" #include "crosstabview.h" #include "describe.h" @@ -42,6 +43,7 @@ #include "input.h" #include "large_obj.h" #include "mainloop.h" +#include "fe_utils/psqlscan_int.h" #include "fe_utils/print.h" #include "psqlscanslash.h" #include "settings.h" @@ -85,6 +87,12 @@ static void checkWin32Codepage(void); #endif +static ConditionalStack +get_conditional_stack(PsqlScanState scan_state) +{ + return (ConditionalStack) scan_state->cb_passthrough; +} + /*---------- * HandleSlashCmds: @@ -111,8 +119,10 @@ HandleSlashCmds(PsqlScanState scan_state, backslashResult status = PSQL_CMD_SKIP_LINE; char *cmd; char *arg; + ConditionalStack cstack = get_conditional_stack(scan_state); Assert(scan_state != NULL); + Assert(cstack != NULL); /* Parse off the command name */ cmd = psql_scan_slash_command(scan_state); @@ -129,7 +139,7 @@ HandleSlashCmds(PsqlScanState scan_state, status = PSQL_CMD_ERROR; } - if (status != PSQL_CMD_ERROR) + if (status != PSQL_CMD_ERROR && conditional_active(cstack)) { /* eat any remaining arguments after a valid command */ /* note we suppress evaluation of backticks here */ @@ -191,33 +201,157 @@ read_connect_arg(PsqlScanState scan_state) return result; } +/* + * Read a boolean expression. + * Expand variables/backticks if expansion is true. + * Issue warning for nonstandard number of options is warn is true. + */ +static PQExpBuffer +gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn) +{ + enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL; + PQExpBuffer exp_buf = createPQExpBuffer(); + int num_options = 0; + char *value; + + /* digest all options for the conditional command */ + while ((value = psql_scan_slash_option(scan_state, slash_opt, NULL, false))) + { + /* add a space in between subsequent tokens */ + if (num_options > 0) + appendPQExpBufferChar(exp_buf, ' '); + appendPQExpBufferStr(exp_buf, value); + num_options++; + } + + /* currently, we expect exactly one option */ + if (warn) + { + if (num_options == 0) + psql_error("WARNING: Boolean expression with no options"); + else if (num_options > 1) + psql_error("WARNING: Boolean expression with %d options: %s\n", + num_options, exp_buf->data); + } + + return exp_buf; +} /* - * Subroutine to actually try to execute a backslash command. + * Read a boolean expression, but do nothing with it. */ -static backslashResult -exec_command(const char *cmd, - PsqlScanState scan_state, - PQExpBuffer query_buf) +static void +ignore_boolean_expression(PsqlScanState scan_state) { - bool success = true; /* indicate here if the command ran ok or - * failed */ - backslashResult status = PSQL_CMD_SKIP_LINE; + destroyPQExpBuffer(gather_boolean_expression(scan_state, false, false)); +} - /* - * \a -- toggle field alignment This makes little sense but we keep it - * around. - */ - if (strcmp(cmd, "a") == 0) +/* + * Read and interpret argument as a boolean expression. + * Return true if a boolean value was successfully parsed. + * Do not clobber result if parse was not successful. + */ +static bool +read_boolean_expression(PsqlScanState scan_state, char *action, + bool expansion, bool *result) +{ + PQExpBuffer expr = gather_boolean_expression(scan_state, true, true); + bool success = ParseVariableBool(expr->data, action, result); + destroyPQExpBuffer(expr); + return success; +} + +/* + * Read a boolean expression, return true if the expression + * was a valid boolean expression that evaluated to true. + * Otherwise return false. + */ +static bool +is_true_boolean_expression(PsqlScanState scan_state, char *action) +{ + bool tf; + bool success = read_boolean_expression(scan_state, action, true, &tf); + return success && tf; +} + +/* + * Return true if the command given is a branching command. + */ +static bool +is_branching_command(const char *cmd) +{ + return (strcmp(cmd, "if") == 0 || strcmp(cmd, "elif") == 0 + || strcmp(cmd, "else") == 0 || strcmp(cmd, "endif") == 0); +} + +/* + * Convenience routine to indicate if the current branch is active + */ +static bool +is_active_branch(PsqlScanState scan_state) +{ + return conditional_active(get_conditional_stack(scan_state)); +} + +/* + * Ignore exactly one slash command option. Return true if an option was found + */ +static bool +ignore_slash_option(PsqlScanState scan_state) +{ + char *p = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, false); + if (!p) + return false; + free(p); + return true; +} + +/* + * Ignore up to two slash command options. + */ +static void +ignore_2_slash_options(PsqlScanState scan_state) +{ + if (ignore_slash_option(scan_state)) + ignore_slash_option(scan_state); +} + +/* + * Ignore exactly one whole-line slash command option. + */ +static void +ignore_slash_line(PsqlScanState scan_state) +{ + char *p = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); + if (p) + free(p); +} + +/* + * \a -- toggle field alignment This makes little sense but we keep it + * around. + */ +static bool +exec_command_a(PsqlScanState scan_state) +{ + bool success = true; + if (is_active_branch(scan_state)) { if (pset.popt.topt.format != PRINT_ALIGNED) success = do_pset("format", "aligned", &pset.popt, pset.quiet); else success = do_pset("format", "unaligned", &pset.popt, pset.quiet); } + return success; +} - /* \C -- override table title (formerly change HTML caption) */ - else if (strcmp(cmd, "C") == 0) +/* \C -- override table title (formerly change HTML caption) */ +static bool +exec_command_C(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); @@ -225,61 +359,78 @@ exec_command(const char *cmd, success = do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_option(scan_state); - /* - * \c or \connect -- connect to database using the specified parameters. - * - * \c [-reuse-previous=BOOL] dbname user host port - * - * Specifying a parameter as '-' is equivalent to omitting it. Examples: - * - * \c - - hst Connect to current database on current port of host - * "hst" as current user. \c - usr - prt Connect to current database on - * "prt" port of current host as user "usr". \c dbs Connect to - * "dbs" database on current port of current host as current user. - */ - else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) + return success; +} + + +/* + * \c or \connect -- connect to database using the specified parameters. + * + * \c [-reuse-previous=BOOL] dbname user host port + * + * Specifying a parameter as '-' is equivalent to omitting it. Examples: + * + * \c - - hst Connect to current database on current port of host + * "hst" as current user. \c - usr - prt Connect to current database on + * "prt" port of current host as user "usr". \c dbs Connect to + * "dbs" database on current port of current host as current user. + */ +static bool +exec_command_connect(PsqlScanState scan_state) +{ + static const char prefix[] = "-reuse-previous="; + char *opt1, + *opt2, + *opt3, + *opt4; + enum trivalue reuse_previous = TRI_DEFAULT; + bool success = true; + bool active_branch = is_active_branch(scan_state); + + opt1 = read_connect_arg(scan_state); + if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0) { - static const char prefix[] = "-reuse-previous="; - char *opt1, - *opt2, - *opt3, - *opt4; - enum trivalue reuse_previous = TRI_DEFAULT; + bool on_off; - opt1 = read_connect_arg(scan_state); - if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0) + success = ParseVariableBool(opt1 + sizeof(prefix) - 1, + ((active_branch) ? + "-reuse-previous" : NULL), + &on_off); + if (success) { - bool on_off; - - success = ParseVariableBool(opt1 + sizeof(prefix) - 1, - "-reuse-previous", - &on_off); - if (success) - { - reuse_previous = on_off ? TRI_YES : TRI_NO; - free(opt1); - opt1 = read_connect_arg(scan_state); - } + reuse_previous = on_off ? TRI_YES : TRI_NO; + free(opt1); + opt1 = read_connect_arg(scan_state); } + } - if (success) /* give up if reuse_previous was invalid */ - { - opt2 = read_connect_arg(scan_state); - opt3 = read_connect_arg(scan_state); - opt4 = read_connect_arg(scan_state); + if (success) /* give up if reuse_previous was invalid */ + { + opt2 = read_connect_arg(scan_state); + opt3 = read_connect_arg(scan_state); + opt4 = read_connect_arg(scan_state); + if (active_branch) success = do_connect(reuse_previous, opt1, opt2, opt3, opt4); - free(opt2); - free(opt3); - free(opt4); - } - free(opt1); + free(opt2); + free(opt3); + free(opt4); } + free(opt1); + return success; +} - /* \cd */ - else if (strcmp(cmd, "cd") == 0) +/* \cd */ +static bool +exec_command_cd(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); @@ -323,9 +474,19 @@ exec_command(const char *cmd, if (opt) free(opt); } + else + ignore_slash_option(scan_state); - /* \conninfo -- display information about the current connection */ - else if (strcmp(cmd, "conninfo") == 0) + return success; +} + +/* \conninfo -- display information about the current connection */ +static bool +exec_command_conninfo(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char *db = PQdb(pset.db); @@ -365,37 +526,67 @@ exec_command(const char *cmd, PQconninfoFree(connOptions); } } + return success; +} - /* \copy */ - else if (pg_strcasecmp(cmd, "copy") == 0) - { - char *opt = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, false); +/* \copy */ +static bool +exec_command_copy(PsqlScanState scan_state) +{ + bool success = true; + char *opt = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, false); + if (is_active_branch(scan_state)) success = do_copy(opt); - free(opt); - } + free(opt); - /* \copyright */ - else if (strcmp(cmd, "copyright") == 0) + return success; +} + + +/* \copyright */ +static bool +exec_command_copyright(PsqlScanState scan_state) +{ + if (is_active_branch(scan_state)) print_copyright(); + return true; +} - /* \crosstabview -- execute a query and display results in crosstab */ - else if (strcmp(cmd, "crosstabview") == 0) - { - int i; +/* \crosstabview -- execute a query and display results in crosstab */ +static bool +exec_command_crosstabview(PsqlScanState scan_state, backslashResult *status) +{ + int i; + + if (is_active_branch(scan_state)) + { for (i = 0; i < lengthof(pset.ctv_args); i++) pset.ctv_args[i] = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); pset.crosstab_flag = true; - status = PSQL_CMD_SEND; + *status = PSQL_CMD_SEND; + } + else + { + for (i = 0; i < lengthof(pset.ctv_args); i++) + ignore_slash_option(scan_state); } + return true; +} - /* \d* commands */ - else if (cmd[0] == 'd') +/* \d* commands */ +static bool +exec_command_d(PsqlScanState scan_state, const char *cmd, + backslashResult *status) +{ + char *pattern = NULL; + bool success = true; + + if (is_active_branch(scan_state)) { - char *pattern; bool show_verbose, show_system; @@ -454,7 +645,7 @@ exec_command(const char *cmd, success = describeFunctions(&cmd[2], pattern, show_verbose, show_system); break; default: - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; break; } break; @@ -517,7 +708,7 @@ exec_command(const char *cmd, success = describeSubscriptions(pattern, show_verbose); break; default: - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; } break; case 'u': @@ -540,7 +731,7 @@ exec_command(const char *cmd, success = listTSTemplates(pattern, show_verbose); break; default: - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; break; } break; @@ -560,7 +751,7 @@ exec_command(const char *cmd, success = listForeignTables(pattern, show_verbose); break; default: - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; break; } break; @@ -574,24 +765,37 @@ exec_command(const char *cmd, success = listEventTriggers(pattern, show_verbose); break; default: - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; } - - if (pattern) - free(pattern); + } + else + { + /* digest and discard options as appropriate */ + pattern = psql_scan_slash_option(scan_state, OT_NO_EVAL, NULL, true); + if (pattern && cmd[1] == 'r' && cmd[2] == 'd' && cmd[3] == 's') + ignore_slash_option(scan_state); } + if (pattern) + free(pattern); - /* - * \e or \edit -- edit the current query buffer, or edit a file and make - * it the query buffer - */ - else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) + return success; +} + +/* + * \e or \edit -- edit the current query buffer, or edit a file and make + * it the query buffer + */ +static bool +exec_command_edit(PsqlScanState scan_state, PQExpBuffer query_buf, + backslashResult *status) +{ + if (is_active_branch(scan_state)) { if (!query_buf) { psql_error("no query buffer\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -624,18 +828,18 @@ exec_command(const char *cmd, if (lineno < 1) { psql_error("invalid line number: %s\n", ln); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } } - if (status != PSQL_CMD_ERROR) + if (*status != PSQL_CMD_ERROR) { expand_tilde(&fname); if (fname) canonicalize_path(fname); if (do_edit(fname, query_buf, lineno, NULL)) - status = PSQL_CMD_NEWEDIT; + *status = PSQL_CMD_NEWEDIT; else - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } if (fname) free(fname); @@ -643,12 +847,25 @@ exec_command(const char *cmd, free(ln); } } + else + { + if (query_buf) + ignore_2_slash_options(scan_state); + } - /* - * \ef -- edit the named function, or present a blank CREATE FUNCTION - * template if no argument is given - */ - else if (strcmp(cmd, "ef") == 0) + return true; +} + + +/* + * \ef -- edit the named function, or present a blank CREATE FUNCTION + * template if no argument is given + */ +static bool +exec_command_ef(PsqlScanState scan_state, PQExpBuffer query_buf, + backslashResult *status) +{ + if (is_active_branch(scan_state)) { int lineno = -1; @@ -659,12 +876,12 @@ exec_command(const char *cmd, psql_error("The server (version %s) does not support editing function source.\n", formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!query_buf) { psql_error("no query buffer\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -677,7 +894,7 @@ exec_command(const char *cmd, if (lineno == 0) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!func) { @@ -693,12 +910,12 @@ exec_command(const char *cmd, else if (!lookup_object_oid(EditableFunction, func, &foid)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(EditableFunction, foid, query_buf)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (lineno > 0) { @@ -730,24 +947,33 @@ exec_command(const char *cmd, free(func); } - if (status != PSQL_CMD_ERROR) + if (*status != PSQL_CMD_ERROR) { bool edited = false; if (!do_edit(NULL, query_buf, lineno, &edited)) - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; else if (!edited) puts(_("No changes")); else - status = PSQL_CMD_NEWEDIT; + *status = PSQL_CMD_NEWEDIT; } } + else + ignore_slash_line(scan_state); - /* - * \ev -- edit the named view, or present a blank CREATE VIEW template if - * no argument is given - */ - else if (strcmp(cmd, "ev") == 0) + return true; +} + +/* + * \ev -- edit the named view, or present a blank CREATE VIEW template if + * no argument is given + */ +static bool +exec_command_ev(PsqlScanState scan_state, PQExpBuffer query_buf, + backslashResult *status) +{ + if (is_active_branch(scan_state)) { int lineno = -1; @@ -758,12 +984,12 @@ exec_command(const char *cmd, psql_error("The server (version %s) does not support editing view definitions.\n", formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!query_buf) { psql_error("no query buffer\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -776,7 +1002,7 @@ exec_command(const char *cmd, if (lineno == 0) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!view) { @@ -789,35 +1015,44 @@ exec_command(const char *cmd, else if (!lookup_object_oid(EditableView, view, &view_oid)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(EditableView, view_oid, query_buf)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } if (view) free(view); } - if (status != PSQL_CMD_ERROR) + if (*status != PSQL_CMD_ERROR) { bool edited = false; if (!do_edit(NULL, query_buf, lineno, &edited)) - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; else if (!edited) puts(_("No changes")); else - status = PSQL_CMD_NEWEDIT; + *status = PSQL_CMD_NEWEDIT; } } + else + ignore_slash_line(scan_state); - /* \echo and \qecho */ - else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) + return true; +} + +/* \echo and \qecho */ +static bool +exec_command_echo(PsqlScanState scan_state, const char *cmd) +{ + char *value; + + if (is_active_branch(scan_state)) { - char *value; char quoted; bool no_newline = false; bool first = true; @@ -846,12 +1081,24 @@ exec_command(const char *cmd, if (!no_newline) fputs("\n", fout); } + else + { + while ((value = psql_scan_slash_option(scan_state, + OT_NO_EVAL, NULL, false))) + free(value); + } - /* \encoding -- set/show client side encoding */ - else if (strcmp(cmd, "encoding") == 0) + return true; +} + +/* \encoding -- set/show client side encoding */ +static bool +exec_command_encoding(PsqlScanState scan_state) +{ + + if (is_active_branch(scan_state)) { - char *encoding = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!encoding) { @@ -874,9 +1121,17 @@ exec_command(const char *cmd, free(encoding); } } + else + ignore_slash_option(scan_state); - /* \errverbose -- display verbose message from last failed query */ - else if (strcmp(cmd, "errverbose") == 0) + return true; +} + +/* \errverbose -- display verbose message from last failed query */ +static bool +exec_command_errverbose(PsqlScanState scan_state) +{ + if (is_active_branch(scan_state)) { if (pset.last_error_result) { @@ -896,25 +1151,39 @@ exec_command(const char *cmd, else puts(_("There is no previous error.")); } + return true; +} - /* \f -- change field separator */ - else if (strcmp(cmd, "f") == 0) +/* \f -- change field separator */ +static bool +exec_command_f(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } + else + ignore_slash_option(scan_state); - /* - * \g [filename] -- send query, optionally with output to file/pipe - * \gx [filename] -- same as \g, with expanded mode forced - */ - else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0) + return success; +} + +/* + * \g [filename] -- send query, optionally with output to file/pipe + * \gx [filename] -- same as \g, with expanded mode forced + */ +static bool +exec_command_g(PsqlScanState scan_state, const char *cmd, backslashResult *status) +{ + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, false); + char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); if (!fname) pset.gfname = NULL; @@ -926,21 +1195,33 @@ exec_command(const char *cmd, free(fname); if (strcmp(cmd, "gx") == 0) pset.g_expanded = true; - status = PSQL_CMD_SEND; + *status = PSQL_CMD_SEND; } + else + ignore_slash_option(scan_state); - /* \gexec -- send query and execute each field of result */ - else if (strcmp(cmd, "gexec") == 0) + return true; +} + +/* \gexec -- send query and execute each field of result */ +static bool +exec_command_gexec(PsqlScanState scan_state, backslashResult *status) +{ + if (is_active_branch(scan_state)) { pset.gexec_flag = true; - status = PSQL_CMD_SEND; + *status = PSQL_CMD_SEND; } + return true; +} - /* \gset [prefix] -- send query and store result into variables */ - else if (strcmp(cmd, "gset") == 0) +/* \gset [prefix] -- send query and store result into variables */ +static bool +exec_command_gset(PsqlScanState scan_state, backslashResult *status) +{ + if (is_active_branch(scan_state)) { - char *prefix = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (prefix) pset.gset_prefix = prefix; @@ -950,15 +1231,23 @@ exec_command(const char *cmd, pset.gset_prefix = pg_strdup(""); } /* gset_prefix is freed later */ - status = PSQL_CMD_SEND; + *status = PSQL_CMD_SEND; } + else + ignore_slash_option(scan_state); - /* help */ - else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) - { - char *opt = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, false); - size_t len; + return true; +} + +/* help */ +static bool +exec_command_help(PsqlScanState scan_state) +{ + + if (is_active_branch(scan_state)) + { + size_t len; + char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); /* strip any trailing spaces and semicolons */ if (opt) @@ -973,9 +1262,19 @@ exec_command(const char *cmd, helpSQL(opt, pset.popt.topt.pager); free(opt); } + else + ignore_slash_line(scan_state); - /* HTML mode */ - else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) + return true; +} + +/* HTML mode */ +static bool +exec_command_html(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { if (pset.popt.topt.format != PRINT_HTML) success = do_pset("format", "html", &pset.popt, pset.quiet); @@ -983,13 +1282,19 @@ exec_command(const char *cmd, success = do_pset("format", "aligned", &pset.popt, pset.quiet); } + return success; +} + - /* \i and \ir include files */ - else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 - || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) +/* \i and \ir include files */ +static bool +exec_command_include(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (!fname) { @@ -1007,16 +1312,185 @@ exec_command(const char *cmd, free(fname); } } + else + ignore_slash_option(scan_state); - /* \l is list databases */ - else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 || - strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) + return success; +} + +/* + * \if <expr> is the beginning of an \if..\endif block. <expr> is parsed as + * a boolean expression. Invalid expressions will emit a warning and be + * treated as false. Statements that follow a false expression will be parsed + * but ignored. Note that in the case where an \if statment is itself within + * a false/ignored section of a block, then the entire \if..\endif block will + * be parsed but ignored. + */ +static bool +exec_command_if(PsqlScanState scan_state) +{ + ConditionalStack cstack = get_conditional_stack(scan_state); + + switch (conditional_stack_peek(cstack)) + { + case IFSTATE_IGNORED: + case IFSTATE_FALSE: + case IFSTATE_ELSE_FALSE: + /* new if-block, expression should not be evaluated, + * but call it in silent mode to digest options */ + ignore_boolean_expression(scan_state); + conditional_stack_push(cstack, IFSTATE_IGNORED); + break; + default: + /* new if-block, check expression for truth. */ + if (is_true_boolean_expression(scan_state, "\\if <expr>")) + conditional_stack_push(cstack, IFSTATE_TRUE); + else + conditional_stack_push(cstack, IFSTATE_FALSE); + break; + } + + return true; +} + +/* + * \elif <expr> is part of an \if..\endif block. <expr> is evaluated + * same as \if <expr>. + */ +static bool +exec_command_elif(PsqlScanState scan_state) +{ + ConditionalStack cstack = get_conditional_stack(scan_state); + bool success = true; + + switch (conditional_stack_peek(cstack)) + { + case IFSTATE_IGNORED: + /* + * inactive branch, digest expression and move on. + * either if-endif already had a true section, + * or whole block is false. + */ + ignore_boolean_expression(scan_state); + break; + case IFSTATE_TRUE: + /* + * just finished true section of this if-endif, digest + * expression, but then ignore the rest until \endif + */ + ignore_boolean_expression(scan_state); + conditional_stack_poke(cstack, IFSTATE_IGNORED); + break; + case IFSTATE_FALSE: + /* + * have not yet found a true section in this if-endif, + * this might be the first. + */ + if (is_true_boolean_expression(scan_state, "\\elif <expr>")) + conditional_stack_poke(cstack, IFSTATE_TRUE); + break; + case IFSTATE_NONE: + /* no if to elif from */ + psql_error("\\elif: no matching \\if\n"); + success = false; + break; + case IFSTATE_ELSE_TRUE: + case IFSTATE_ELSE_FALSE: + psql_error("\\elif: cannot occur after \\else\n"); + success = false; + break; + default: + break; + } + + return success; +} + +/* + * \else is part of an \if..\endif block + * the statements within an \else branch will only be executed if + * all previous \if and \endif expressions evaluated to false + * and the block was not itself being ignored. + */ +static bool +exec_command_else(PsqlScanState scan_state) +{ + ConditionalStack cstack = get_conditional_stack(scan_state); + bool success = true; + + switch (conditional_stack_peek(cstack)) + { + case IFSTATE_FALSE: + /* just finished false section of an active branch */ + conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE); + break; + case IFSTATE_TRUE: + case IFSTATE_IGNORED: + /* + * either just finished true section of an active branch, + * or whole branch was inactive. either way, be on the + * lookout for any invalid \endif or \else commands + */ + conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); + break; + case IFSTATE_NONE: + /* no if to else from */ + psql_error("\\else: no matching \\if\n"); + success = false; + break; + case IFSTATE_ELSE_TRUE: + case IFSTATE_ELSE_FALSE: + psql_error("\\else: cannot occur after \\else\n"); + success = false; + break; + default: + break; + } + + return success; +} + +/* + * \endif - closing statment of an \if...\endif block + */ +static bool +exec_command_endif(PsqlScanState scan_state) +{ + ConditionalStack cstack = get_conditional_stack(scan_state); + bool success = true; + + /* + * get rid of this ifstate element and look at the previous + * one, if any + */ + switch (conditional_stack_peek(cstack)) + { + case IFSTATE_NONE: + psql_error("\\endif: no matching \\if\n"); + success = false; + break; + default: + success = conditional_stack_pop(cstack); + Assert(success); + break; + } + + return success; +} + + +/* \l is list databases */ +static bool +exec_command_list(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char *pattern; bool show_verbose; - pattern = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); show_verbose = strchr(cmd, '+') ? true : false; @@ -1025,19 +1499,25 @@ exec_command(const char *cmd, if (pattern) free(pattern); } + else + ignore_slash_option(scan_state); - /* - * large object things - */ - else if (strncmp(cmd, "lo_", 3) == 0) - { - char *opt1, - *opt2; - opt1 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); - opt2 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + return success; +} + +/* + * large object things + */ +static bool +exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status) +{ + bool success = true; + + if (is_active_branch(scan_state)) + { + char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + char *opt2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (strcmp(cmd + 3, "export") == 0) { @@ -1082,26 +1562,43 @@ exec_command(const char *cmd, } else - status = PSQL_CMD_UNKNOWN; + *status = PSQL_CMD_UNKNOWN; free(opt1); free(opt2); } + else + ignore_2_slash_options(scan_state); + return success; +} - /* \o -- set query output */ - else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) + +/* \o -- set query output */ +static bool +exec_command_out(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, true); + char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); success = setQFout(fname); free(fname); } + else + ignore_slash_option(scan_state); - /* \p prints the current query buffer */ - else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) + return success; +} + +/* \p prints the current query buffer */ +static bool +exec_command_print(PsqlScanState scan_state, PQExpBuffer query_buf) +{ + if (is_active_branch(scan_state)) { if (query_buf && query_buf->len > 0) puts(query_buf->data); @@ -1110,8 +1607,17 @@ exec_command(const char *cmd, fflush(stdout); } - /* \password -- set user password */ - else if (strcmp(cmd, "password") == 0) + return true; +} + + +/* \password -- set user password */ +static bool +exec_command_password(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char pw1[100]; char pw2[100]; @@ -1164,17 +1670,24 @@ exec_command(const char *cmd, free(opt0); } } + else + ignore_slash_option(scan_state); - /* \prompt -- prompt and set variable */ - else if (strcmp(cmd, "prompt") == 0) + return success; +} + +/* \prompt -- prompt and set variable */ +static bool +exec_command_prompt(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { char *opt, *prompt_text = NULL; - char *arg1, - *arg2; - - arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); - arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + char *arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + char *arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!arg1) { @@ -1225,14 +1738,22 @@ exec_command(const char *cmd, free(opt); } } + else + ignore_2_slash_options(scan_state); - /* \pset -- set printing parameters */ - else if (strcmp(cmd, "pset") == 0) + return success; +} + +/* \pset -- set printing parameters */ +static bool +exec_command_pset(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt0 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); - char *opt1 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { @@ -1267,13 +1788,27 @@ exec_command(const char *cmd, free(opt0); free(opt1); } + else + ignore_2_slash_options(scan_state); - /* \q or \quit */ - else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) - status = PSQL_CMD_TERMINATE; + return success; +} - /* reset(clear) the buffer */ - else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) +/* \q or \quit */ +static bool +exec_command_quit(PsqlScanState scan_state, backslashResult *status) +{ + if (is_active_branch(scan_state)) + *status = PSQL_CMD_TERMINATE; + + return true; +} + +/* reset(clear) the buffer */ +static bool +exec_command_reset(PsqlScanState scan_state, PQExpBuffer query_buf) +{ + if (is_active_branch(scan_state)) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); @@ -1281,11 +1816,18 @@ exec_command(const char *cmd, puts(_("Query buffer reset (cleared).")); } - /* \s save history in a file or show it on the screen */ - else if (strcmp(cmd, "s") == 0) + return true; +} + +/* \s save history in a file or show it on the screen */ +static bool +exec_command_s(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); expand_tilde(&fname); success = printHistory(fname, pset.popt.topt.pager); @@ -1295,12 +1837,21 @@ exec_command(const char *cmd, putchar('\n'); free(fname); } + else + ignore_slash_option(scan_state); - /* \set -- generalized set variable/option command */ - else if (strcmp(cmd, "set") == 0) + return success; +} + +/* \set -- generalized set variable/option command */ +static bool +exec_command_set(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt0 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { @@ -1336,15 +1887,23 @@ exec_command(const char *cmd, } free(opt0); } + else + while (ignore_slash_option(scan_state)) + continue; + return success; +} - /* \setenv -- set environment command */ - else if (strcmp(cmd, "setenv") == 0) +/* \setenv -- set environment command */ +static bool +exec_command_setenv(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *envvar = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); - char *envval = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + char *envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!envvar) { @@ -1381,14 +1940,24 @@ exec_command(const char *cmd, free(envvar); free(envval); } + else + ignore_2_slash_options(scan_state); - /* \sf -- show a function's source code */ - else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) + return success; +} + +/* \sf -- show a function's source code */ +static bool +exec_command_sf(PsqlScanState scan_state, const char *cmd, + backslashResult *status) +{ + + if (is_active_branch(scan_state)) { bool show_linenumbers = (strcmp(cmd, "sf+") == 0); PQExpBuffer func_buf; - char *func; Oid foid = InvalidOid; + char *func; func_buf = createPQExpBuffer(); func = psql_scan_slash_option(scan_state, @@ -1400,22 +1969,22 @@ exec_command(const char *cmd, psql_error("The server (version %s) does not support showing function source.\n", formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!func) { psql_error("function name is required\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!lookup_object_oid(EditableFunction, func, &foid)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(EditableFunction, foid, func_buf)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -1463,14 +2032,24 @@ exec_command(const char *cmd, free(func); destroyPQExpBuffer(func_buf); } + else + ignore_slash_line(scan_state); - /* \sv -- show a view's source code */ - else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) + return true; +} + +/* \sv -- show a view's source code */ +static bool +exec_command_sv(PsqlScanState scan_state, const char *cmd, + backslashResult *status) +{ + + if (is_active_branch(scan_state)) { bool show_linenumbers = (strcmp(cmd, "sv+") == 0); PQExpBuffer view_buf; - char *view; Oid view_oid = InvalidOid; + char *view; view_buf = createPQExpBuffer(); view = psql_scan_slash_option(scan_state, @@ -1482,22 +2061,22 @@ exec_command(const char *cmd, psql_error("The server (version %s) does not support showing view definitions.\n", formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!view) { psql_error("view name is required\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!lookup_object_oid(EditableView, view, &view_oid)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(EditableView, view_oid, view_buf)) { /* error already reported */ - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -1539,32 +2118,59 @@ exec_command(const char *cmd, free(view); destroyPQExpBuffer(view_buf); } + else + ignore_slash_line(scan_state); - /* \t -- turn off headers and row count */ - else if (strcmp(cmd, "t") == 0) + return true; +} + +/* \t -- turn off headers and row count */ +static bool +exec_command_t(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_option(scan_state); - /* \T -- define html <table ...> attributes */ - else if (strcmp(cmd, "T") == 0) + return success; +} + +/* \T -- define html <table ...> attributes */ +static bool +exec_command_T(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *value = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } + else + ignore_slash_option(scan_state); - /* \timing -- toggle timing of queries */ - else if (strcmp(cmd, "timing") == 0) + return success; +} + +/* \timing -- toggle timing of queries */ +static bool +exec_command_timing(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (opt) success = ParseVariableBool(opt, "\\timing", &pset.timing); @@ -1579,12 +2185,21 @@ exec_command(const char *cmd, } free(opt); } + else + ignore_slash_option(scan_state); - /* \unset */ - else if (strcmp(cmd, "unset") == 0) + return success; +} + +/* \unset */ +static bool +exec_command_unset(PsqlScanState scan_state, const char *cmd) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt) { @@ -1596,18 +2211,31 @@ exec_command(const char *cmd, free(opt); } + else + ignore_slash_option(scan_state); - /* \w -- write query buffer to file */ - else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) + return success; +} + + + +/* \w -- write query buffer to file */ +static bool +exec_command_write(PsqlScanState scan_state, const char *cmd, + PQExpBuffer query_buf, backslashResult *status) +{ + bool success = true; + char *fname = NULL; + + if (is_active_branch(scan_state)) { FILE *fd = NULL; bool is_pipe = false; - char *fname = NULL; if (!query_buf) { psql_error("no query buffer\n"); - status = PSQL_CMD_ERROR; + *status = PSQL_CMD_ERROR; } else { @@ -1665,14 +2293,32 @@ exec_command(const char *cmd, free(fname); } + else + { + fname = psql_scan_slash_option(scan_state, + OT_FILEPIPE, NULL, true); + if (fname) + free(fname); + } - /* \watch -- execute a query every N seconds */ - else if (strcmp(cmd, "watch") == 0) + return success; +} + + + + +/* \watch -- execute a query every N seconds */ +static bool +exec_command_watch(PsqlScanState scan_state, PQExpBuffer query_buf) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); double sleep = 2; + char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + /* Convert optional sleep-length argument */ if (opt) { @@ -1688,43 +2334,77 @@ exec_command(const char *cmd, resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } + else + ignore_slash_option(scan_state); - /* \x -- set or toggle expanded table representation */ - else if (strcmp(cmd, "x") == 0) + return success; +} + +/* \x -- set or toggle expanded table representation */ +static bool +exec_command_x(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_option(scan_state); - /* \z -- list table rights (equivalent to \dp) */ - else if (strcmp(cmd, "z") == 0) + return success; +} + +/* \z -- list table rights (equivalent to \dp) */ +static bool +exec_command_z(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *pattern = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = permissionsList(pattern); if (pattern) free(pattern); } + else + ignore_slash_option(scan_state); - /* \! -- shell escape */ - else if (strcmp(cmd, "!") == 0) + return success; +} + +/* \! -- shell escape */ +static bool +exec_command_shell_escape(PsqlScanState scan_state) +{ + bool success = true; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, false); + char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); success = do_shell(opt); free(opt); } + else + ignore_slash_line(scan_state); - /* \? -- slash command help */ - else if (strcmp(cmd, "?") == 0) + return success; +} + +/* \? -- slash command help */ +static bool +exec_command_slash_command_help(PsqlScanState scan_state) +{ + if (is_active_branch(scan_state)) { - char *opt0 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0 || strcmp(opt0, "commands") == 0) slashUsage(pset.popt.topt.pager); @@ -1734,8 +2414,141 @@ exec_command(const char *cmd, helpVariables(pset.popt.topt.pager); else slashUsage(pset.popt.topt.pager); + + if (opt0) + free(opt0); + } + else + ignore_slash_option(scan_state); + + return true; +} + + + +/* + * Subroutine to actually try to execute a backslash command. + */ +static backslashResult +exec_command(const char *cmd, + PsqlScanState scan_state, + PQExpBuffer query_buf) +{ + ConditionalStack cstack = get_conditional_stack(scan_state); + bool success = true; /* indicate here if the command ran ok or + * failed */ + backslashResult status = PSQL_CMD_SKIP_LINE; + + if (pset.cur_cmd_interactive && !conditional_active(cstack) && + !is_branching_command(cmd)) + { + psql_error("command ignored, use \\endif or Ctrl-C to exit " + "current branch.\n"); } + if (strcmp(cmd, "a") == 0) + success = exec_command_a(scan_state); + else if (strcmp(cmd, "C") == 0) + success = exec_command_C(scan_state); + else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) + success = exec_command_connect(scan_state); + else if (strcmp(cmd, "cd") == 0) + success = exec_command_cd(scan_state, cmd); + else if (strcmp(cmd, "conninfo") == 0) + success = exec_command_conninfo(scan_state); + else if (pg_strcasecmp(cmd, "copy") == 0) + success = exec_command_copy(scan_state); + else if (strcmp(cmd, "copyright") == 0) + success = exec_command_copyright(scan_state); + else if (strcmp(cmd, "crosstabview") == 0) + success = exec_command_crosstabview(scan_state, &status); + else if (cmd[0] == 'd') + success = exec_command_d(scan_state, cmd, &status); + else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) + success = exec_command_edit(scan_state, query_buf, &status); + else if (strcmp(cmd, "ef") == 0) + success = exec_command_ef(scan_state, query_buf, &status); + else if (strcmp(cmd, "ev") == 0) + success = exec_command_ev(scan_state, query_buf, &status); + else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) + success = exec_command_echo(scan_state, cmd); + else if (strcmp(cmd, "encoding") == 0) + success = exec_command_encoding(scan_state); + else if (strcmp(cmd, "errverbose") == 0) + success = exec_command_errverbose(scan_state); + else if (strcmp(cmd, "f") == 0) + success = exec_command_f(scan_state); + else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0) + success = exec_command_g(scan_state, cmd, &status); + else if (strcmp(cmd, "gexec") == 0) + success = exec_command_gexec(scan_state, &status); + else if (strcmp(cmd, "gset") == 0) + success = exec_command_gset(scan_state, &status); + else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) + success = exec_command_help(scan_state); + else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) + success = exec_command_html(scan_state); + else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 + || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) + success = exec_command_include(scan_state, cmd); + else if (strcmp(cmd, "if") == 0) + success = exec_command_if(scan_state); + else if (strcmp(cmd, "elif") == 0) + success = exec_command_elif(scan_state); + else if (strcmp(cmd, "else") == 0) + success = exec_command_else(scan_state); + else if (strcmp(cmd, "endif") == 0) + success = exec_command_endif(scan_state); + else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 || + strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) + success = exec_command_list(scan_state, cmd); + else if (strncmp(cmd, "lo_", 3) == 0) + success = exec_command_lo(scan_state, cmd, &status); + else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) + success = exec_command_out(scan_state); + else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) + success = exec_command_print(scan_state, query_buf); + else if (strcmp(cmd, "password") == 0) + success = exec_command_password(scan_state); + else if (strcmp(cmd, "prompt") == 0) + success = exec_command_prompt(scan_state, cmd); + else if (strcmp(cmd, "pset") == 0) + success = exec_command_pset(scan_state); + else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) + success = exec_command_quit(scan_state, &status); + else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) + success = exec_command_reset(scan_state, query_buf); + else if (strcmp(cmd, "s") == 0) + success = exec_command_s(scan_state); + else if (strcmp(cmd, "set") == 0) + success = exec_command_set(scan_state); + else if (strcmp(cmd, "setenv") == 0) + success = exec_command_setenv(scan_state, cmd); + else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) + success = exec_command_sf(scan_state, cmd, &status); + else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) + success = exec_command_sv(scan_state, cmd, &status); + else if (strcmp(cmd, "t") == 0) + success = exec_command_t(scan_state); + else if (strcmp(cmd, "T") == 0) + success = exec_command_T(scan_state); + else if (strcmp(cmd, "timing") == 0) + success = exec_command_timing(scan_state); + else if (strcmp(cmd, "unset") == 0) + success = exec_command_unset(scan_state, cmd); + else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) + success = exec_command_write(scan_state, cmd, query_buf, &status); + else if (strcmp(cmd, "watch") == 0) + success = exec_command_watch(scan_state, query_buf); + else if (strcmp(cmd, "x") == 0) + success = exec_command_x(scan_state); + else if (strcmp(cmd, "z") == 0) + success = exec_command_z(scan_state); + else if (strcmp(cmd, "!") == 0) + success = exec_command_shell_escape(scan_state); + else if (strcmp(cmd, "?") == 0) + success = exec_command_slash_command_help(scan_state); + #if 0 /* diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index e9d4fe6..e2299f4 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -24,6 +24,7 @@ #include "settings.h" #include "command.h" +#include "conditional.h" #include "copy.h" #include "crosstabview.h" #include "fe_utils/mbprint.h" @@ -121,7 +122,8 @@ setQFout(const char *fname) * (Failure in escaping should lead to returning NULL.) * * "passthrough" is the pointer previously given to psql_scan_set_passthrough. - * psql currently doesn't use this. + * Currently, passthrough points to a ConditionalStack, but in the future + * it may point to a structure with additional state information. */ char * psql_get_variable(const char *varname, bool escape, bool as_ident, diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c new file mode 100644 index 0000000..8643ff1 --- /dev/null +++ b/src/bin/psql/conditional.c @@ -0,0 +1,103 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2017, PostgreSQL Global Development Group + * + * src/bin/psql/conditional.c + */ +#include "postgres_fe.h" + +#include "conditional.h" + +/* + * create stack + */ +ConditionalStack +conditional_stack_create(void) +{ + ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData)); + cstack->head = NULL; + return cstack; +} + +/* + * destroy stack + */ +void +conditional_stack_destroy(ConditionalStack cstack) +{ + while (conditional_stack_pop(cstack)) + continue; + free(cstack); +} + +/* + * Create a new conditional branch. + */ +void +conditional_stack_push(ConditionalStack cstack, ifState new_state) +{ + IfStackElem *p = (IfStackElem*) pg_malloc(sizeof(IfStackElem)); + p->if_state = new_state; + p->next = cstack->head; + cstack->head = p; +} + +/* + * Destroy the topmost conditional branch. + * Returns false if there was no branch to end. + */ +bool +conditional_stack_pop(ConditionalStack cstack) +{ + IfStackElem *p = cstack->head; + if (!p) + return false; + cstack->head = cstack->head->next; + free(p); + return true; +} + +/* + * Fetch the current state of the top of the stack + */ +ifState +conditional_stack_peek(ConditionalStack cstack) +{ + if (conditional_stack_empty(cstack)) + return IFSTATE_NONE; + return cstack->head->if_state; +} + +/* + * Change the state of the topmost branch. + * Returns false if there was no branch state to set. + */ +bool +conditional_stack_poke(ConditionalStack cstack, ifState new_state) +{ + if (conditional_stack_empty(cstack)) + return false; + cstack->head->if_state = new_state; + return true; +} + +/* + * True if there are no active \if-blocks + */ +bool +conditional_stack_empty(ConditionalStack cstack) +{ + return cstack->head == NULL; +} + +/* + * True if the current conditional block is active, or if there is no + * open \if (ie, we should execute commands normally) + */ +bool +conditional_active(ConditionalStack cstack) +{ + ifState s = conditional_stack_peek(cstack); + return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE; +} diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h new file mode 100644 index 0000000..96dbd7f --- /dev/null +++ b/src/bin/psql/conditional.h @@ -0,0 +1,62 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2017, PostgreSQL Global Development Group + * + * src/bin/psql/conditional.h + */ +#ifndef CONDITIONAL_H +#define CONDITIONAL_H + +/* + * Possible states of a single level of \if block. + */ +typedef enum ifState +{ + IFSTATE_NONE = 0, /* Not currently in an \if block */ + IFSTATE_TRUE, /* currently in an \if or \elif which is true + * and all parent branches (if any) are true */ + IFSTATE_FALSE, /* currently in an \if or \elif which is false + * but no true branch has yet been seen, + * and all parent branches (if any) are true */ + IFSTATE_IGNORED, /* currently in an \elif which follows a true \if + * or the whole \if is a child of a false parent */ + IFSTATE_ELSE_TRUE, /* currently in an \else which is true + * and all parent branches (if any) are true */ + IFSTATE_ELSE_FALSE /* currently in an \else which is false or ignored */ +} ifState; + +/* + * The state of nested \ifs is stored in a stack. + */ +typedef struct IfStackElem +{ + ifState if_state; + struct IfStackElem *next; +} IfStackElem; + +typedef struct ConditionalStackData +{ + IfStackElem *head; +} ConditionalStackData; + +typedef struct ConditionalStackData *ConditionalStack; + + +extern ConditionalStack conditional_stack_create(void); + +extern void conditional_stack_destroy(ConditionalStack cstack); + +extern bool conditional_stack_empty(ConditionalStack cstack); + +extern void conditional_stack_push(ConditionalStack cstack, ifState new_state); + +extern bool conditional_stack_pop(ConditionalStack cstack); + +extern ifState conditional_stack_peek(ConditionalStack cstack); + +extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state); + +extern bool conditional_active(ConditionalStack cstack); + +#endif /* CONDITIONAL_H */ diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index 481031a..2005b9a 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -552,7 +552,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) /* interactive input probably silly, but give one prompt anyway */ if (showprompt) { - const char *prompt = get_prompt(PROMPT_COPY); + const char *prompt = get_prompt(PROMPT_COPY, NULL); fputs(prompt, stdout); fflush(stdout); @@ -590,7 +590,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) if (showprompt) { - const char *prompt = get_prompt(PROMPT_COPY); + const char *prompt = get_prompt(PROMPT_COPY, NULL); fputs(prompt, stdout); fflush(stdout); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index ba14df0..79afafb 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -210,6 +210,13 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n")); fprintf(output, "\n"); + fprintf(output, _("Conditionals\n")); + fprintf(output, _(" \\if <expr> begin a conditional block\n")); + fprintf(output, _(" \\elif <expr> else if in the current conditional block\n")); + fprintf(output, _(" \\else else in the current conditional block\n")); + fprintf(output, _(" \\endif end current conditional block\n")); + fprintf(output, "\n"); + fprintf(output, _("Informational\n")); fprintf(output, _(" (options: S = show system objects, + = additional detail)\n")); fprintf(output, _(" \\d[S+] list tables, views, and sequences\n")); diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c index 6e358e2..e9e1e3f 100644 --- a/src/bin/psql/mainloop.c +++ b/src/bin/psql/mainloop.c @@ -25,6 +25,23 @@ const PsqlScanCallbacks psqlscan_callbacks = { /* + * execute query if branch is active. + * warn interactive users about ignored queries. + */ +static bool +send_query(const char *query, ConditionalStack cstack) +{ + /* execute query if branch is active */ + if (conditional_active(cstack)) + return SendQuery(query); + + if (pset.cur_cmd_interactive) + psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if branch\n"); + + return true; +} + +/* * Main processing loop for reading lines of input * and sending them to the backend. * @@ -35,6 +52,7 @@ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ + ConditionalStack cond_stack; /* \if status stack */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, @@ -69,6 +87,8 @@ MainLoop(FILE *source) /* Create working state */ scan_state = psql_scan_create(&psqlscan_callbacks); + cond_stack = conditional_stack_create(); + psql_scan_set_passthrough(scan_state, (void *) cond_stack); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); @@ -122,7 +142,18 @@ MainLoop(FILE *source) cancel_pressed = false; if (pset.cur_cmd_interactive) + { putc('\n', stdout); + /* + * if interactive user is in a branch, then Ctrl-C will exit + * from the inner-most branch + */ + if (!conditional_stack_empty(cond_stack)) + { + psql_error("\\if: escaped\n"); + conditional_stack_pop(cond_stack); + } + } else { successResult = EXIT_USER; @@ -140,7 +171,8 @@ MainLoop(FILE *source) /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; - line = gets_interactive(get_prompt(prompt_status), query_buf); + line = gets_interactive(get_prompt(prompt_status, cond_stack), + query_buf); } else { @@ -297,7 +329,7 @@ MainLoop(FILE *source) } /* execute query */ - success = SendQuery(query_buf->data); + success = send_query(query_buf->data, cond_stack); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; pset.stmt_lineno = 1; @@ -358,7 +390,7 @@ MainLoop(FILE *source) if (slashCmdStatus == PSQL_CMD_SEND) { - success = SendQuery(query_buf->data); + success = send_query(query_buf->data, cond_stack); /* transfer query to previous_buf by pointer-swapping */ { @@ -430,7 +462,7 @@ MainLoop(FILE *source) pg_send_history(history_buf); /* execute query */ - success = SendQuery(query_buf->data); + success = send_query(query_buf->data, cond_stack); if (!success && die_on_error) successResult = EXIT_USER; @@ -439,6 +471,19 @@ MainLoop(FILE *source) } /* + * Check for unbalanced \if-\endifs unless user explicitly quit, or the + * script is erroring out + */ + if (slashCmdStatus != PSQL_CMD_TERMINATE && + successResult != EXIT_USER && + !conditional_stack_empty(cond_stack)) + { + psql_error("reached EOF without finding closing \\endif(s)\n"); + if (die_on_error && !pset.cur_cmd_interactive) + successResult = EXIT_USER; + } + + /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to @@ -452,6 +497,7 @@ MainLoop(FILE *source) destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); + conditional_stack_destroy(cond_stack); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c index f7930c4..e502ff3 100644 --- a/src/bin/psql/prompt.c +++ b/src/bin/psql/prompt.c @@ -66,7 +66,7 @@ */ char * -get_prompt(promptStatus_t status) +get_prompt(promptStatus_t status, ConditionalStack cstack) { #define MAX_PROMPT_SIZE 256 static char destination[MAX_PROMPT_SIZE + 1]; @@ -188,7 +188,9 @@ get_prompt(promptStatus_t status) switch (status) { case PROMPT_READY: - if (!pset.db) + if (cstack != NULL && !conditional_active(cstack)) + buf[0] = '@'; + else if (!pset.db) buf[0] = '!'; else if (!pset.singleline) buf[0] = '='; diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h index 977e754..b3d2d98 100644 --- a/src/bin/psql/prompt.h +++ b/src/bin/psql/prompt.h @@ -10,7 +10,8 @@ /* enum promptStatus_t is now defined by psqlscan.h */ #include "fe_utils/psqlscan.h" +#include "conditional.h" -char *get_prompt(promptStatus_t status); +char *get_prompt(promptStatus_t status, ConditionalStack cstack); #endif /* PROMPT_H */ diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 694f0ef..7809629 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -18,6 +18,7 @@ #include "command.h" #include "common.h" +#include "conditional.h" #include "describe.h" #include "help.h" #include "input.h" @@ -331,6 +332,7 @@ main(int argc, char *argv[]) else if (cell->action == ACT_SINGLE_SLASH) { PsqlScanState scan_state; + ConditionalStack cond_stack; if (pset.echo == PSQL_ECHO_ALL) puts(cell->val); @@ -339,11 +341,15 @@ main(int argc, char *argv[]) psql_scan_setup(scan_state, cell->val, strlen(cell->val), pset.encoding, standard_strings()); + cond_stack = conditional_stack_create(); + psql_scan_set_passthrough(scan_state, (void *) cond_stack); - successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR + successResult = HandleSlashCmds(scan_state, + NULL) != PSQL_CMD_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; psql_scan_destroy(scan_state); + conditional_stack_destroy(cond_stack); } else if (cell->action == ACT_FILE) { diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index eb7f197..5774c98 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -2735,6 +2735,116 @@ deallocate q; \pset format aligned \pset expanded off \pset border 1 +-- test a large nested if using a variety of true-equivalents +\if true + \if 1 + \if yes + \if on + \echo 'all true' +all true + \else + \echo 'should not print #1-1' + \endif + \else + \echo 'should not print #1-2' + \endif + \else + \echo 'should not print #1-3' + \endif +\else + \echo 'should not print #1-4' +\endif +-- test a variety of false-equivalents in an if/elif/else structure +\if false + \echo 'should not print #2-1' +\elif 0 + \echo 'should not print #2-2' +\elif no + \echo 'should not print #2-3' +\elif off + \echo 'should not print #2-4' +\else + \echo 'all false' +all false +\endif +-- test simple true-then-else +\if true + \echo 'first thing true' +first thing true +\else + \echo 'should not print #3-1' +\endif +-- test simple false-true-else +\if false + \echo 'should not print #4-1' +\elif true + \echo 'second thing true' +second thing true +\else + \echo 'should not print #5-1' +\endif +-- invalid boolean expressions are false +\if invalid_boolean_expression +unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean expected + \echo 'will not print #6-1' +\else + \echo 'will print anyway #6-2' +will print anyway #6-2 +\endif +-- test un-matched endif +\endif +\endif: no matching \if +-- test un-matched else +\else +\else: no matching \if +-- test un-matched elif +\elif +\elif: no matching \if +-- test double-else error +\if true +\else +\else +\else: cannot occur after \else +\endif +-- test elif out-of-order +\if false +\else +\elif +\elif: cannot occur after \else +\endif +-- test if-endif matching in a false branch +\if false + \if false + \echo 'should not print #7-1' + \else + \echo 'should not print #7-2' + \endif + \echo 'should not print #7-3' +\else + \echo 'should print #7-4' +should print #7-4 +\endif +-- show that vars are not expanded and commands are ignored but args are parsed +\set try_to_quit '\\q' +\if false + :try_to_quit + \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo + \copy arg1 arg2 arg3 arg4 arg5 arg6 + \copyright \dt arg1 \e arg1 arg2 + \ef whole_line + \ev whole_line + \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose + \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2 + \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q + \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2 + \sf whole_line + \sv whole_line + \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1 + \! whole_line +\else + \echo 'should print #8-1' +should print #8-1 +\endif -- SHOW_CONTEXT \set SHOW_CONTEXT never do $$ diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 8f8e17a..078a76f 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -382,6 +382,115 @@ deallocate q; \pset expanded off \pset border 1 +-- test a large nested if using a variety of true-equivalents +\if true + \if 1 + \if yes + \if on + \echo 'all true' + \else + \echo 'should not print #1-1' + \endif + \else + \echo 'should not print #1-2' + \endif + \else + \echo 'should not print #1-3' + \endif +\else + \echo 'should not print #1-4' +\endif + +-- test a variety of false-equivalents in an if/elif/else structure +\if false + \echo 'should not print #2-1' +\elif 0 + \echo 'should not print #2-2' +\elif no + \echo 'should not print #2-3' +\elif off + \echo 'should not print #2-4' +\else + \echo 'all false' +\endif + +-- test simple true-then-else +\if true + \echo 'first thing true' +\else + \echo 'should not print #3-1' +\endif + +-- test simple false-true-else +\if false + \echo 'should not print #4-1' +\elif true + \echo 'second thing true' +\else + \echo 'should not print #5-1' +\endif + +-- invalid boolean expressions are false +\if invalid_boolean_expression + \echo 'will not print #6-1' +\else + \echo 'will print anyway #6-2' +\endif + +-- test un-matched endif +\endif + +-- test un-matched else +\else + +-- test un-matched elif +\elif + +-- test double-else error +\if true +\else +\else +\endif + +-- test elif out-of-order +\if false +\else +\elif +\endif + +-- test if-endif matching in a false branch +\if false + \if false + \echo 'should not print #7-1' + \else + \echo 'should not print #7-2' + \endif + \echo 'should not print #7-3' +\else + \echo 'should print #7-4' +\endif + +-- show that vars are not expanded and commands are ignored but args are parsed +\set try_to_quit '\\q' +\if false + :try_to_quit + \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo + \copy arg1 arg2 arg3 arg4 arg5 arg6 + \copyright \dt arg1 \e arg1 arg2 + \ef whole_line + \ev whole_line + \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose + \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2 + \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q + \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2 + \sf whole_line + \sv whole_line + \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1 + \! whole_line +\else + \echo 'should print #8-1' +\endif + -- SHOW_CONTEXT \set SHOW_CONTEXT never -- 2.7.4
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers