> > Patches do not apply cleanly. > Part 1 gets: > error: patch failed: src/test/regress/parallel_schedule:89 > error: src/test/regress/parallel_schedule: patch does not apply > > There is still the useless file, ok it is removed by part2. Could have > been just one patch... >
parallel_schedule failed because I hadn't rebased recently enough. git format-patch did us no favors there. New patch is redone as one commit. ISTM that PQExpBuffer is partially a memory leak. Something should need to > be freed? > I copied that pattern from somewhere else, so yeah, I duplicated whatever leak was there. Fixed. > I think that you should use appendPQExpBufferChar and Str instead of > relying on the format variant which is probably expensive. Something like: > > if (num_options > 0) > append...Char(buf, ' '); > append...Str(buf, ...); > All flavors of appendPQExpBuffer*() I can find have a const *char format string, so no way to append a naked string. If you know differently, I'm listening. Not fixed. > > is_true_boolean_expression: "return (success) ? tf : false;" > Is this simply: "return success && tf;"? > Neat. Done. > > Some functions have opt1, opt2, but some start at opt0. This does not look > too consistent, although the inconsistency may be preexisting from your > patch. Basically, there is a need for some more restructuring in > "command.c". It is pre-existing. Maybe this patch will inspire someone else to make the other more consistent. v26 attached
From fc0c466b0331839efe722d089a8ead521e8a827e Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@moat.com> Date: Sun, 26 Mar 2017 17:30:51 -0400 Subject: [PATCH] psql if v26 --- doc/src/sgml/ref/psql-ref.sgml | 90 +- src/bin/psql/.gitignore | 2 + src/bin/psql/Makefile | 4 +- src/bin/psql/command.c | 1383 ++++++++++++++++---- 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 ++ .../regress/expected/psql_if_on_error_stop.out | 14 + src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/psql.sql | 109 ++ src/test/regress/sql/psql_if_on_error_stop.sql | 17 + 19 files changed, 1699 insertions(+), 284 deletions(-) create mode 100644 src/bin/psql/conditional.c create mode 100644 src/bin/psql/conditional.h create mode 100644 src/test/regress/expected/psql_if_on_error_stop.out create mode 100644 src/test/regress/sql/psql_if_on_error_stop.sql 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..aa9dad4 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,158 @@ 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) + appendPQExpBuffer(exp_buf, "%s", value); + else + appendPQExpBuffer(exp_buf, " %s", 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 +360,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 +475,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 +527,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 +646,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 +709,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 +732,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 +752,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 +766,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 +829,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 +848,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 +877,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 +895,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 +911,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 +948,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 +985,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 +1003,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 +1016,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 +1082,25 @@ 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) +{ + char *encoding; + + if (is_active_branch(scan_state)) { - char *encoding = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!encoding) { @@ -874,9 +1123,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 +1153,41 @@ 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; + char *fname; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + 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) +{ + char *fname; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, false); + fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); if (!fname) pset.gfname = NULL; @@ -926,21 +1199,34 @@ 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) +{ + char *prefix; + if (is_active_branch(scan_state)) { - char *prefix = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (prefix) pset.gset_prefix = prefix; @@ -950,16 +1236,26 @@ 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); + + return true; +} + +/* help */ +static bool +exec_command_help(PsqlScanState scan_state) +{ + char *opt; + + if (is_active_branch(scan_state)) + { + size_t len; + + opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); - /* 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; - /* strip any trailing spaces and semicolons */ if (opt) { @@ -973,9 +1269,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 +1289,20 @@ 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; + char *fname; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (!fname) { @@ -1007,16 +1320,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,15 +1507,24 @@ 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; + return success; +} + +/* + * large object things + */ +static bool +exec_command_lo(PsqlScanState scan_state, const char *cmd, backslashResult *status) +{ + bool success = true; + char *opt1, *opt2; + + if (is_active_branch(scan_state)) + { opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); opt2 = psql_scan_slash_option(scan_state, @@ -1082,26 +1573,44 @@ 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; + char *fname; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, true); + 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 +1619,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,14 +1682,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; + char *arg1, + *arg2; + + 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); @@ -1225,14 +1753,24 @@ 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; + char *opt0, + *opt1; + + 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); + opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { @@ -1267,13 +1805,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 +1833,19 @@ 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; + char *fname; + + if (is_active_branch(scan_state)) { - char *fname = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); expand_tilde(&fname); success = printHistory(fname, pset.popt.topt.pager); @@ -1295,12 +1855,22 @@ 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; + char *opt0; + + if (is_active_branch(scan_state)) { - char *opt0 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { @@ -1336,15 +1906,25 @@ 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; + char *envvar; + char *envval; + + 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); + envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); + envval = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!envvar) { @@ -1381,13 +1961,23 @@ 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) +{ + char *func; + + if (is_active_branch(scan_state)) { bool show_linenumbers = (strcmp(cmd, "sf+") == 0); PQExpBuffer func_buf; - char *func; Oid foid = InvalidOid; func_buf = createPQExpBuffer(); @@ -1400,22 +1990,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,13 +2053,23 @@ 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) +{ + char *view; + + if (is_active_branch(scan_state)) { bool show_linenumbers = (strcmp(cmd, "sv+") == 0); PQExpBuffer view_buf; - char *view; Oid view_oid = InvalidOid; view_buf = createPQExpBuffer(); @@ -1482,22 +2082,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 +2139,62 @@ 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + 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; + char *value; + + if (is_active_branch(scan_state)) { - char *value = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (opt) success = ParseVariableBool(opt, "\\timing", &pset.timing); @@ -1579,11 +2209,22 @@ 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, + opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt) @@ -1596,18 +2237,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 +2319,33 @@ 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); double sleep = 2; + opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + /* Convert optional sleep-length argument */ if (opt) { @@ -1688,43 +2361,82 @@ 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + 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; + char *pattern; + + if (is_active_branch(scan_state)) { - char *pattern = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + 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; + char *opt; + + if (is_active_branch(scan_state)) { - char *opt = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, false); + 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) +{ + char *opt0 = NULL; + + if (is_active_branch(scan_state)) { - char *opt0 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, false); + opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0 || strcmp(opt0, "commands") == 0) slashUsage(pset.popt.topt.pager); @@ -1734,8 +2446,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/expected/psql_if_on_error_stop.out b/src/test/regress/expected/psql_if_on_error_stop.out new file mode 100644 index 0000000..c9dd3c7 --- /dev/null +++ b/src/test/regress/expected/psql_if_on_error_stop.out @@ -0,0 +1,14 @@ +-- +-- Test psql \if-errors respecting ON_ERROR_STOP +-- +-- Tests for psql features that aren't closely connected to any +-- specific server features +-- +\set ON_ERROR_STOP on +\if true + \echo ok +ok +\else + \echo error at 1.1 +\elif +\elif: cannot occur after \else diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 9f95b01..9a9ac1c 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview # ---------- # Another group of parallel tests # ---------- -test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext +test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext psql_if_on_error_stop # rules cannot run concurrently with any test that creates a view test: rules psql_crosstab amutils diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index e026b7c..75fa328 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -124,6 +124,7 @@ test: alter_generic test: alter_operator test: misc test: psql +test: psql_if_on_error_stop test: async test: dbsize test: misc_functions 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 diff --git a/src/test/regress/sql/psql_if_on_error_stop.sql b/src/test/regress/sql/psql_if_on_error_stop.sql new file mode 100644 index 0000000..864ebac --- /dev/null +++ b/src/test/regress/sql/psql_if_on_error_stop.sql @@ -0,0 +1,17 @@ +-- +-- Test psql \if-errors respecting ON_ERROR_STOP +-- +-- Tests for psql features that aren't closely connected to any +-- specific server features +-- +\set ON_ERROR_STOP on +\if true + \echo ok +\else + \echo error at 1.1 +\elif + \echo error at 1.2 +\else + \echo error at 1.3 +\endif +\echo error at 1.4 -- 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