Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless) - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless) |
Date | |
Msg-id | 10990.1490847359@sss.pgh.pa.us Whole thread Raw |
In response to | Re: \if, \elseif, \else, \endif (was Re: PSQL commands:\quit_if, \quit_unless) (Fabien COELHO <coelho@cri.ensmp.fr>) |
Responses |
Re: \if, \elseif, \else, \endif (was Re: PSQL commands:\quit_if, \quit_unless)
Re: \if, \elseif, \else, \endif (was Re: PSQL commands:\quit_if, \quit_unless) |
List | pgsql-hackers |
Fabien COELHO <coelho@cri.ensmp.fr> writes: >> New Patch v29: Now with less coverage! > Patch applies cleanly. Make check ok. Feature still works! I've been hacking on this for about two full days now, and have gotten it to a point where I think it's committable. Aside from cosmetic changes, I've made it behave reasonably for cases where \if is used on portions of a query, for instance SELECT \if :something var1 \else var2 \endif FROM table; which as I mentioned a long time ago is something that people will certainly expect to work. I also cleaned up a lot of corner-case discrepancies between how much text is consumed in active-branch and inactive-branch cases (OT_FILEPIPE is a particularly nasty case in that regard :-() I plan to read this over again tomorrow and then push it, if there are not objections/corrections. regards, tom lane diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 2a9c412..b51b11b 100644 *** a/doc/src/sgml/ref/psql-ref.sgml --- b/doc/src/sgml/ref/psql-ref.sgml *************** hello 10 *** 2064,2069 **** --- 2064,2158 ---- <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 + their argument(s) and evaluate them as a boolean expression. If the + expression yields <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> test has + succeeded, the arguments of later <command>\elif</command> commands in + the same block 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> + The <replaceable class="parameter">expression</replaceable> argument + of an <command>\if</command> or <command>\elif</command> 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> + 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. + Variable references in skipped lines are not expanded, and backquote + expansion is not performed either. + </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> *************** testdb=> <userinput>INSERT INTO my_ta *** 3715,3721 **** <listitem> <para> In prompt 1 normally <literal>=</literal>, ! but <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 --- 3804,3811 ---- <listitem> <para> In prompt 1 normally <literal>=</literal>, ! but <literal>@</literal> if the session is in an inactive branch of a ! 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/Makefile b/src/bin/psql/Makefile index f8e31ea..ab2cfa6 100644 *** a/src/bin/psql/Makefile --- b/src/bin/psql/Makefile *************** REFDOCDIR= $(top_srcdir)/doc/src/sgml/re *** 21,30 **** 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 \ ! startup.o prompt.o variables.o large_obj.o describe.o \ ! crosstabview.o tab-complete.o \ ! sql_help.o psqlscanslash.o \ $(WIN32RES) --- 21,30 ---- override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq ! OBJS= command.o common.o conditional.o copy.o crosstabview.o \ ! describe.o help.o input.o large_obj.o mainloop.o \ ! prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \ ! tab-complete.o variables.o \ $(WIN32RES) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 4f4a0aa..e278511 100644 *** a/src/bin/psql/command.c --- b/src/bin/psql/command.c *************** typedef enum EditableObjectType *** 56,69 **** EditableView } EditableObjectType; ! /* functions for use in this file */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, ! PQExpBuffer query_buf); ! static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, ! int lineno, bool *edited); static bool do_connect(enum trivalue reuse_previous_specification, char *dbname, char *user, char *host, char *port); static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, double sleep); static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, --- 56,158 ---- EditableView } EditableObjectType; ! /* local function declarations */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf); ! static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf, PQExpBuffer previous_buf); ! static backslashResult exec_command_ef(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_ev(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_sf(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_sv(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch, ! const char *cmd); ! static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch, ! const char *cmd, ! PQExpBuffer query_buf, PQExpBuffer previous_buf); ! static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf, PQExpBuffer previous_buf); ! static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch); ! static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch); ! static char *read_connect_arg(PsqlScanState scan_state); ! static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state); ! static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name); ! static void ignore_boolean_expression(PsqlScanState scan_state); ! static void ignore_slash_options(PsqlScanState scan_state); ! static void ignore_slash_filepipe(PsqlScanState scan_state); ! static void ignore_slash_whole_line(PsqlScanState scan_state); ! static bool is_branching_command(const char *cmd); ! static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf); ! static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf); static bool do_connect(enum trivalue reuse_previous_specification, char *dbname, char *user, char *host, char *port); + static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, + int lineno, bool *edited); static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, double sleep); static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, *************** static void checkWin32Codepage(void); *** 96,104 **** * just after the '\'. The lexer is advanced past the command and all * arguments on return. * ! * 'query_buf' contains the query-so-far, which may be modified by * execution of the backslash command (for example, \r clears it). ! * query_buf can be NULL if there is no query so far. * * Returns a status code indicating what action is desired, see command.h. *---------- --- 185,202 ---- * just after the '\'. The lexer is advanced past the command and all * arguments on return. * ! * cstack is the current \if stack state. This will be examined, and ! * possibly modified by conditional commands. ! * ! * query_buf contains the query-so-far, which may be modified by * execution of the backslash command (for example, \r clears it). ! * ! * previous_buf contains the query most recently sent to the server ! * (empty if none yet). This should not be modified here, but some ! * commands copy its content into query_buf. ! * ! * query_buf and previous_buf will be NULL when executing a "-c" ! * command-line option. * * Returns a status code indicating what action is desired, see command.h. *---------- *************** static void checkWin32Codepage(void); *** 106,124 **** backslashResult HandleSlashCmds(PsqlScanState scan_state, ! PQExpBuffer query_buf) { ! backslashResult status = PSQL_CMD_SKIP_LINE; char *cmd; char *arg; Assert(scan_state != NULL); /* Parse off the command name */ cmd = psql_scan_slash_command(scan_state); /* And try to execute it */ ! status = exec_command(cmd, scan_state, query_buf); if (status == PSQL_CMD_UNKNOWN) { --- 204,225 ---- backslashResult HandleSlashCmds(PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf) { ! backslashResult status; char *cmd; char *arg; Assert(scan_state != NULL); + Assert(cstack != NULL); /* Parse off the command name */ cmd = psql_scan_slash_command(scan_state); /* And try to execute it */ ! status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf); if (status == PSQL_CMD_UNKNOWN) { *************** HandleSlashCmds(PsqlScanState scan_state *** 131,144 **** if (status != PSQL_CMD_ERROR) { ! /* eat any remaining arguments after a valid command */ ! /* note we suppress evaluation of backticks here */ while ((arg = psql_scan_slash_option(scan_state, ! OT_NO_EVAL, NULL, false))) { ! psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg); free(arg); } } else { --- 232,253 ---- if (status != PSQL_CMD_ERROR) { ! /* ! * Eat any remaining arguments after a valid command. We want to ! * suppress evaluation of backticks in this situation, so transiently ! * push an inactive conditional-stack entry. ! */ ! bool active_branch = conditional_active(cstack); ! ! conditional_stack_push(cstack, IFSTATE_IGNORED); while ((arg = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false))) { ! if (active_branch) ! psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg); free(arg); } + conditional_stack_pop(cstack); } else { *************** HandleSlashCmds(PsqlScanState scan_state *** 159,214 **** return status; } /* ! * Read and interpret an argument to the \connect slash command. */ ! static char * ! read_connect_arg(PsqlScanState scan_state) { ! char *result; ! char quote; /* ! * Ideally we should treat the arguments as SQL identifiers. But for ! * backwards compatibility with 7.2 and older pg_dump files, we have to ! * take unquoted arguments verbatim (don't downcase them). For now, ! * double-quoted arguments may be stripped of double quotes (as if SQL ! * identifiers). By 7.4 or so, pg_dump files can be expected to ! * double-quote all mixed-case \connect arguments, and then we can get rid ! * of OT_SQLIDHACK. */ ! result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true); ! ! if (!result) ! return NULL; ! if (quote) ! return result; ! if (*result == '\0' || strcmp(result, "-") == 0) ! return NULL; ! return result; } /* ! * Subroutine to actually try to execute a backslash command. */ static backslashResult ! exec_command(const char *cmd, ! PsqlScanState scan_state, ! PQExpBuffer query_buf) { ! bool success = true; /* indicate here if the command ran ok or ! * failed */ ! backslashResult status = PSQL_CMD_SKIP_LINE; ! /* ! * \a -- toggle field alignment This makes little sense but we keep it ! * around. ! */ ! if (strcmp(cmd, "a") == 0) { if (pset.popt.topt.format != PRINT_ALIGNED) success = do_pset("format", "aligned", &pset.popt, pset.quiet); --- 268,436 ---- return status; } + /* ! * Subroutine to actually try to execute a backslash command. ! * ! * The typical "success" result code is PSQL_CMD_SKIP_LINE, although some ! * commands return something else. Failure results are PSQL_CMD_ERROR, ! * unless PSQL_CMD_UNKNOWN is more appropriate. */ ! static backslashResult ! exec_command(const char *cmd, ! PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf) { ! backslashResult status; ! bool active_branch = conditional_active(cstack); /* ! * In interactive mode, warn when we're ignoring a command within a false ! * \if-branch. But we continue on, so as to parse and discard the right ! * number of parameters. Each individual backslash command subroutine is ! * responsible for doing nothing after discarding appropriate arguments, ! * if !active_branch. */ ! if (pset.cur_cmd_interactive && !active_branch && ! !is_branching_command(cmd)) ! { ! psql_error("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block\n", ! cmd); ! } ! if (strcmp(cmd, "a") == 0) ! status = exec_command_a(scan_state, active_branch); ! else if (strcmp(cmd, "C") == 0) ! status = exec_command_C(scan_state, active_branch); ! else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) ! status = exec_command_connect(scan_state, active_branch); ! else if (strcmp(cmd, "cd") == 0) ! status = exec_command_cd(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "conninfo") == 0) ! status = exec_command_conninfo(scan_state, active_branch); ! else if (pg_strcasecmp(cmd, "copy") == 0) ! status = exec_command_copy(scan_state, active_branch); ! else if (strcmp(cmd, "copyright") == 0) ! status = exec_command_copyright(scan_state, active_branch); ! else if (strcmp(cmd, "crosstabview") == 0) ! status = exec_command_crosstabview(scan_state, active_branch); ! else if (cmd[0] == 'd') ! status = exec_command_d(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) ! status = exec_command_edit(scan_state, active_branch, ! query_buf, previous_buf); ! else if (strcmp(cmd, "ef") == 0) ! status = exec_command_ef(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "ev") == 0) ! status = exec_command_ev(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) ! status = exec_command_echo(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "elif") == 0) ! status = exec_command_elif(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "else") == 0) ! status = exec_command_else(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "endif") == 0) ! status = exec_command_endif(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "encoding") == 0) ! status = exec_command_encoding(scan_state, active_branch); ! else if (strcmp(cmd, "errverbose") == 0) ! status = exec_command_errverbose(scan_state, active_branch); ! else if (strcmp(cmd, "f") == 0) ! status = exec_command_f(scan_state, active_branch); ! else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0) ! status = exec_command_g(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "gexec") == 0) ! status = exec_command_gexec(scan_state, active_branch); ! else if (strcmp(cmd, "gset") == 0) ! status = exec_command_gset(scan_state, active_branch); ! else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) ! status = exec_command_help(scan_state, active_branch); ! else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) ! status = exec_command_html(scan_state, active_branch); ! else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 || ! strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) ! status = exec_command_include(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "if") == 0) ! status = exec_command_if(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 || ! strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) ! status = exec_command_list(scan_state, active_branch, cmd); ! else if (strncmp(cmd, "lo_", 3) == 0) ! status = exec_command_lo(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) ! status = exec_command_out(scan_state, active_branch); ! else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) ! status = exec_command_print(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "password") == 0) ! status = exec_command_password(scan_state, active_branch); ! else if (strcmp(cmd, "prompt") == 0) ! status = exec_command_prompt(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "pset") == 0) ! status = exec_command_pset(scan_state, active_branch); ! else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) ! status = exec_command_quit(scan_state, active_branch); ! else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) ! status = exec_command_reset(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "s") == 0) ! status = exec_command_s(scan_state, active_branch); ! else if (strcmp(cmd, "set") == 0) ! status = exec_command_set(scan_state, active_branch); ! else if (strcmp(cmd, "setenv") == 0) ! status = exec_command_setenv(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) ! status = exec_command_sf(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) ! status = exec_command_sv(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "t") == 0) ! status = exec_command_t(scan_state, active_branch); ! else if (strcmp(cmd, "T") == 0) ! status = exec_command_T(scan_state, active_branch); ! else if (strcmp(cmd, "timing") == 0) ! status = exec_command_timing(scan_state, active_branch); ! else if (strcmp(cmd, "unset") == 0) ! status = exec_command_unset(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) ! status = exec_command_write(scan_state, active_branch, cmd, ! query_buf, previous_buf); ! else if (strcmp(cmd, "watch") == 0) ! status = exec_command_watch(scan_state, active_branch, ! query_buf, previous_buf); ! else if (strcmp(cmd, "x") == 0) ! status = exec_command_x(scan_state, active_branch); ! else if (strcmp(cmd, "z") == 0) ! status = exec_command_z(scan_state, active_branch); ! else if (strcmp(cmd, "!") == 0) ! status = exec_command_shell_escape(scan_state, active_branch); ! else if (strcmp(cmd, "?") == 0) ! status = exec_command_slash_command_help(scan_state, active_branch); ! else ! status = PSQL_CMD_UNKNOWN; ! /* ! * All the commands that return PSQL_CMD_SEND want to execute previous_buf ! * if query_buf is empty. For convenience we implement that here, not in ! * the individual command subroutines. ! */ ! if (status == PSQL_CMD_SEND) ! copy_previous_query(query_buf, previous_buf); ! return status; } /* ! * \a -- toggle field alignment ! * ! * This makes little sense but we keep it around. */ static backslashResult ! exec_command_a(PsqlScanState scan_state, bool active_branch) { ! bool success = true; ! if (active_branch) { if (pset.popt.topt.format != PRINT_ALIGNED) success = do_pset("format", "aligned", &pset.popt, pset.quiet); *************** exec_command(const char *cmd, *** 216,223 **** success = do_pset("format", "unaligned", &pset.popt, pset.quiet); } ! /* \C -- override table title (formerly change HTML caption) */ ! else if (strcmp(cmd, "C") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 438,455 ---- success = do_pset("format", "unaligned", &pset.popt, pset.quiet); } ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \C -- override table title (formerly change HTML caption) ! */ ! static backslashResult ! exec_command_C(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 225,244 **** success = do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } ! /* ! * \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) { static const char prefix[] = "-reuse-previous="; char *opt1, --- 457,488 ---- success = do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \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 port "prt" of current host ! * as user "usr". ! * \c dbs Connect to database "dbs" on current port of current host ! * as current user. ! */ ! static backslashResult ! exec_command_connect(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { static const char prefix[] = "-reuse-previous="; char *opt1, *************** exec_command(const char *cmd, *** 277,285 **** } free(opt1); } ! /* \cd */ ! else if (strcmp(cmd, "cd") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 521,541 ---- } free(opt1); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \cd -- change directory ! */ ! static backslashResult ! exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 323,331 **** if (opt) free(opt); } ! /* \conninfo -- display information about the current connection */ ! else if (strcmp(cmd, "conninfo") == 0) { char *db = PQdb(pset.db); --- 579,597 ---- if (opt) free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \conninfo -- display information about the current connection ! */ ! static backslashResult ! exec_command_conninfo(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *db = PQdb(pset.db); *************** exec_command(const char *cmd, *** 366,373 **** } } ! /* \copy */ ! else if (pg_strcasecmp(cmd, "copy") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 632,649 ---- } } ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \copy -- run a COPY command ! */ ! static backslashResult ! exec_command_copy(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); *************** exec_command(const char *cmd, *** 375,387 **** success = do_copy(opt); free(opt); } ! /* \copyright */ ! else if (strcmp(cmd, "copyright") == 0) print_copyright(); ! /* \crosstabview -- execute a query and display results in crosstab */ ! else if (strcmp(cmd, "crosstabview") == 0) { int i; --- 651,683 ---- success = do_copy(opt); free(opt); } + else + ignore_slash_whole_line(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \copyright ! */ ! static backslashResult ! exec_command_copyright(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) print_copyright(); ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \crosstabview -- execute a query and display results in crosstab ! */ ! static backslashResult ! exec_command_crosstabview(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { int i; *************** exec_command(const char *cmd, *** 391,399 **** pset.crosstab_flag = true; status = PSQL_CMD_SEND; } ! /* \d* commands */ ! else if (cmd[0] == 'd') { char *pattern; bool show_verbose, --- 687,708 ---- pset.crosstab_flag = true; status = PSQL_CMD_SEND; } + else + ignore_slash_options(scan_state); ! return status; ! } ! ! /* ! * \d* commands ! */ ! static backslashResult ! exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! bool success = true; ! ! if (active_branch) { char *pattern; bool show_verbose, *************** exec_command(const char *cmd, *** 502,508 **** success = listDbRoleSettings(pattern, pattern2); } else ! success = PSQL_CMD_UNKNOWN; break; case 'R': switch (cmd[2]) --- 811,817 ---- success = listDbRoleSettings(pattern, pattern2); } else ! status = PSQL_CMD_UNKNOWN; break; case 'R': switch (cmd[2]) *************** exec_command(const char *cmd, *** 580,592 **** 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) { if (!query_buf) { --- 889,914 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); + if (!success) + status = PSQL_CMD_ERROR; ! return status; ! } ! ! /* ! * \e or \edit -- edit the current query buffer, or edit a file and ! * make it the query buffer ! */ ! static backslashResult ! exec_command_edit(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf, PQExpBuffer previous_buf) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { if (!query_buf) { *************** exec_command(const char *cmd, *** 632,637 **** --- 954,963 ---- expand_tilde(&fname); if (fname) canonicalize_path(fname); + + /* Applies to previous query if current buffer is empty */ + copy_previous_query(query_buf, previous_buf); + if (do_edit(fname, query_buf, lineno, NULL)) status = PSQL_CMD_NEWEDIT; else *************** exec_command(const char *cmd, *** 643,655 **** free(ln); } } ! /* ! * \ef -- edit the named function, or present a blank CREATE FUNCTION ! * template if no argument is given ! */ ! else if (strcmp(cmd, "ef") == 0) { int lineno = -1; if (pset.sversion < 80400) --- 969,994 ---- free(ln); } } + else + ignore_slash_options(scan_state); ! return status; ! } ! ! /* ! * \ef -- edit the named function, or present a blank CREATE FUNCTION ! * template if no argument is given ! */ ! static backslashResult ! exec_command_ef(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { + char *func = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); int lineno = -1; if (pset.sversion < 80400) *************** exec_command(const char *cmd, *** 668,678 **** } else { - char *func; Oid foid = InvalidOid; - func = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, true); lineno = strip_lineno_from_objdesc(func); if (lineno == 0) { --- 1007,1014 ---- *************** exec_command(const char *cmd, *** 725,733 **** lines++; } } - - if (func) - free(func); } if (status != PSQL_CMD_ERROR) --- 1061,1066 ---- *************** exec_command(const char *cmd, *** 741,754 **** else status = PSQL_CMD_NEWEDIT; } } ! /* ! * \ev -- edit the named view, or present a blank CREATE VIEW template if ! * no argument is given ! */ ! else if (strcmp(cmd, "ev") == 0) { int lineno = -1; if (pset.sversion < 70400) --- 1074,1103 ---- else status = PSQL_CMD_NEWEDIT; } + + if (func) + free(func); } + else + ignore_slash_whole_line(scan_state); ! return status; ! } ! ! /* ! * \ev -- edit the named view, or present a blank CREATE VIEW ! * template if no argument is given ! */ ! static backslashResult ! exec_command_ev(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { + char *view = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); int lineno = -1; if (pset.sversion < 70400) *************** exec_command(const char *cmd, *** 767,777 **** } else { - char *view; Oid view_oid = InvalidOid; - view = psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, true); lineno = strip_lineno_from_objdesc(view); if (lineno == 0) { --- 1116,1123 ---- *************** exec_command(const char *cmd, *** 796,804 **** /* error already reported */ status = PSQL_CMD_ERROR; } - - if (view) - free(view); } if (status != PSQL_CMD_ERROR) --- 1142,1147 ---- *************** exec_command(const char *cmd, *** 812,821 **** else status = PSQL_CMD_NEWEDIT; } } ! /* \echo and \qecho */ ! else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) { char *value; char quoted; --- 1155,1177 ---- else status = PSQL_CMD_NEWEDIT; } + + if (view) + free(view); } + else + ignore_slash_whole_line(scan_state); ! return status; ! } ! ! /* ! * \echo and \qecho -- echo arguments to stdout or query output ! */ ! static backslashResult ! exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! if (active_branch) { char *value; char quoted; *************** exec_command(const char *cmd, *** 846,854 **** if (!no_newline) fputs("\n", fout); } ! /* \encoding -- set/show client side encoding */ ! else if (strcmp(cmd, "encoding") == 0) { char *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1202,1220 ---- if (!no_newline) fputs("\n", fout); } + else + ignore_slash_options(scan_state); ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \encoding -- set/show client side encoding ! */ ! static backslashResult ! exec_command_encoding(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 874,882 **** free(encoding); } } ! /* \errverbose -- display verbose message from last failed query */ ! else if (strcmp(cmd, "errverbose") == 0) { if (pset.last_error_result) { --- 1240,1258 ---- free(encoding); } } + else + ignore_slash_options(scan_state); ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \errverbose -- display verbose message from last failed query ! */ ! static backslashResult ! exec_command_errverbose(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { if (pset.last_error_result) { *************** exec_command(const char *cmd, *** 897,904 **** puts(_("There is no previous error.")); } ! /* \f -- change field separator */ ! else if (strcmp(cmd, "f") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1273,1290 ---- puts(_("There is no previous error.")); } ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \f -- change field separator ! */ ! static backslashResult ! exec_command_f(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 906,917 **** success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } ! /* ! * \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) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); --- 1292,1313 ---- success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \g [filename] -- send query, optionally with output to file/pipe ! * \gx [filename] -- same as \g, with expanded mode forced ! */ ! static backslashResult ! exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); *************** exec_command(const char *cmd, *** 928,943 **** pset.g_expanded = true; status = PSQL_CMD_SEND; } ! /* \gexec -- send query and execute each field of result */ ! else if (strcmp(cmd, "gexec") == 0) { pset.gexec_flag = true; status = PSQL_CMD_SEND; } ! /* \gset [prefix] -- send query and store result into variables */ ! else if (strcmp(cmd, "gset") == 0) { char *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1324,1361 ---- pset.g_expanded = true; status = PSQL_CMD_SEND; } + else + ignore_slash_filepipe(scan_state); ! return status; ! } ! ! /* ! * \gexec -- send query and execute each field of result ! */ ! static backslashResult ! exec_command_gexec(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { pset.gexec_flag = true; status = PSQL_CMD_SEND; } ! return status; ! } ! ! /* ! * \gset [prefix] -- send query and store result into variables ! */ ! static backslashResult ! exec_command_gset(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { char *prefix = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 952,960 **** /* gset_prefix is freed later */ status = PSQL_CMD_SEND; } ! /* help */ ! else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 1370,1388 ---- /* gset_prefix is freed later */ status = PSQL_CMD_SEND; } + else + ignore_slash_options(scan_state); ! return status; ! } ! ! /* ! * \help [topic] -- print help about SQL commands ! */ ! static backslashResult ! exec_command_help(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); *************** exec_command(const char *cmd, *** 973,981 **** helpSQL(opt, pset.popt.topt.pager); free(opt); } ! /* HTML mode */ ! else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) { if (pset.popt.topt.format != PRINT_HTML) success = do_pset("format", "html", &pset.popt, pset.quiet); --- 1401,1421 ---- helpSQL(opt, pset.popt.topt.pager); free(opt); } + else + ignore_slash_whole_line(scan_state); ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \H and \html -- toggle HTML formatting ! */ ! static backslashResult ! exec_command_html(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { if (pset.popt.topt.format != PRINT_HTML) success = do_pset("format", "html", &pset.popt, pset.quiet); *************** exec_command(const char *cmd, *** 983,992 **** success = do_pset("format", "aligned", &pset.popt, pset.quiet); } ! /* \i and \ir include files */ ! else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 ! || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 1423,1440 ---- success = do_pset("format", "aligned", &pset.popt, pset.quiet); } + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; + } ! /* ! * \i and \ir -- include a file ! */ ! static backslashResult ! exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1007,1016 **** free(fname); } } ! /* \l is list databases */ ! else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 || ! strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) { char *pattern; bool show_verbose; --- 1455,1708 ---- free(fname); } } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \if <expr> -- 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 statement ! * is itself within an inactive section of a block, then the entire inner ! * \if..\endif block will be parsed but ignored. ! */ ! static backslashResult ! exec_command_if(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! if (conditional_active(cstack)) ! { ! /* ! * First, push a new active stack entry; this ensures that the lexer ! * will perform variable substitution and backtick evaluation while ! * scanning the expression. (That should happen anyway, since we know ! * we're in an active outer branch, but let's be sure.) ! */ ! conditional_stack_push(cstack, IFSTATE_TRUE); ! ! /* Remember current query state in case we need to restore later */ ! save_query_text_state(scan_state, cstack, query_buf); ! ! /* ! * Evaluate the expression; if it's false, change to inactive state. ! */ ! if (!is_true_boolean_expression(scan_state, "\\if expression")) ! conditional_stack_poke(cstack, IFSTATE_FALSE); ! } ! else ! { ! /* ! * We're within an inactive outer branch, so this entire \if block ! * will be ignored. We don't want to evaluate the expression, so push ! * the "ignored" stack state before scanning it. ! */ ! conditional_stack_push(cstack, IFSTATE_IGNORED); ! ! /* Remember current query state in case we need to restore later */ ! save_query_text_state(scan_state, cstack, query_buf); ! ! ignore_boolean_expression(scan_state); ! } ! ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \elif <expr> -- alternative branch in an \if..\endif block ! * ! * <expr> is evaluated the same as in \if <expr>. ! */ ! static backslashResult ! exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! bool success = true; ! ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: ! ! /* ! * Just finished active branch of this \if block. Update saved ! * state so we will keep whatever data was put in query_buf by the ! * active branch. ! */ ! save_query_text_state(scan_state, cstack, query_buf); ! ! /* ! * Discard \elif expression and ignore the rest until \endif. ! * Switch state before reading expression to ensure proper lexer ! * behavior. ! */ ! conditional_stack_poke(cstack, IFSTATE_IGNORED); ! ignore_boolean_expression(scan_state); ! break; ! case IFSTATE_FALSE: ! ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); ! ! /* ! * Have not yet found a true expression in this \if block, so this ! * might be the first. We have to change state before examining ! * the expression, or the lexer won't do the right thing. ! */ ! conditional_stack_poke(cstack, IFSTATE_TRUE); ! if (!is_true_boolean_expression(scan_state, "\\elif expression")) ! conditional_stack_poke(cstack, IFSTATE_FALSE); ! break; ! case IFSTATE_IGNORED: ! ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); ! ! /* ! * Skip expression and move on. Either the \if block already had ! * an active section, or whole block is being skipped. ! */ ! ignore_boolean_expression(scan_state); ! break; ! case IFSTATE_ELSE_TRUE: ! case IFSTATE_ELSE_FALSE: ! psql_error("\\elif: cannot occur after \\else\n"); ! success = false; ! break; ! case IFSTATE_NONE: ! /* no \if to elif from */ ! psql_error("\\elif: no matching \\if\n"); ! success = false; ! break; ! } ! ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \else -- final alternative in an \if..\endif block ! * ! * Statements within an \else branch will only be executed if ! * all previous \if and \elif expressions evaluated to false ! * and the block was not itself being ignored. ! */ ! static backslashResult ! exec_command_else(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! bool success = true; ! ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: ! ! /* ! * Just finished active branch of this \if block. Update saved ! * state so we will keep whatever data was put in query_buf by the ! * active branch. ! */ ! save_query_text_state(scan_state, cstack, query_buf); ! ! /* Now skip the \else branch */ ! conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); ! break; ! case IFSTATE_FALSE: ! ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); ! ! /* ! * We've not found any true \if or \elif expression, so execute ! * the \else branch. ! */ ! conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE); ! break; ! case IFSTATE_IGNORED: ! ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); ! ! /* ! * Either we previously processed the active branch of this \if, ! * or the whole \if block is being skipped. Either way, skip the ! * \else branch. ! */ ! conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); ! break; ! case IFSTATE_ELSE_TRUE: ! case IFSTATE_ELSE_FALSE: ! psql_error("\\else: cannot occur after \\else\n"); ! success = false; ! break; ! case IFSTATE_NONE: ! /* no \if to else from */ ! psql_error("\\else: no matching \\if\n"); ! success = false; ! break; ! } ! ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \endif -- ends an \if...\endif block ! */ ! static backslashResult ! exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! bool success = true; ! ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: ! case IFSTATE_ELSE_TRUE: ! /* Close the \if block, keeping the query text */ ! success = conditional_stack_pop(cstack); ! Assert(success); ! break; ! case IFSTATE_FALSE: ! case IFSTATE_IGNORED: ! case IFSTATE_ELSE_FALSE: ! ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); ! ! /* Close the \if block */ ! success = conditional_stack_pop(cstack); ! Assert(success); ! break; ! case IFSTATE_NONE: ! /* no \if to end */ ! psql_error("\\endif: no matching \\if\n"); ! success = false; ! break; ! } ! ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \l -- list databases ! */ ! static backslashResult ! exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *pattern; bool show_verbose; *************** exec_command(const char *cmd, *** 1025,1035 **** if (pattern) free(pattern); } ! /* ! * large object things ! */ ! else if (strncmp(cmd, "lo_", 3) == 0) { char *opt1, *opt2; --- 1717,1738 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \lo_* -- large object operations ! */ ! static backslashResult ! exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! bool success = true; ! ! if (active_branch) { char *opt1, *opt2; *************** exec_command(const char *cmd, *** 1087,1096 **** free(opt1); free(opt2); } ! /* \o -- set query output */ ! else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); --- 1790,1813 ---- free(opt1); free(opt2); } + else + ignore_slash_options(scan_state); + if (!success) + status = PSQL_CMD_ERROR; ! return status; ! } ! ! /* ! * \o -- set query output ! */ ! static backslashResult ! exec_command_out(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); *************** exec_command(const char *cmd, *** 1099,1107 **** success = setQFout(fname); free(fname); } ! /* \p prints the current query buffer */ ! else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) { if (query_buf && query_buf->len > 0) puts(query_buf->data); --- 1816,1835 ---- success = setQFout(fname); free(fname); } + else + ignore_slash_filepipe(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \p -- print the current query buffer ! */ ! static backslashResult ! exec_command_print(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf) ! { ! if (active_branch) { if (query_buf && query_buf->len > 0) puts(query_buf->data); *************** exec_command(const char *cmd, *** 1110,1118 **** fflush(stdout); } ! /* \password -- set user password */ ! else if (strcmp(cmd, "password") == 0) { char pw1[100]; char pw2[100]; --- 1838,1858 ---- fflush(stdout); } ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \password -- set user password ! */ ! static backslashResult ! exec_command_password(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { + char *opt0 = psql_scan_slash_option(scan_state, + OT_SQLID, NULL, true); char pw1[100]; char pw2[100]; *************** exec_command(const char *cmd, *** 1126,1132 **** } else { - char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true); char *user; char *encrypted_password; --- 1866,1871 ---- *************** exec_command(const char *cmd, *** 1159,1172 **** PQclear(res); PQfreemem(encrypted_password); } - - if (opt0) - free(opt0); } } ! /* \prompt -- prompt and set variable */ ! else if (strcmp(cmd, "prompt") == 0) { char *opt, *prompt_text = NULL; --- 1898,1924 ---- PQclear(res); PQfreemem(encrypted_password); } } + + if (opt0) + free(opt0); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \prompt -- prompt and set variable ! */ ! static backslashResult ! exec_command_prompt(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *opt, *prompt_text = NULL; *************** exec_command(const char *cmd, *** 1225,1233 **** free(opt); } } ! /* \pset -- set printing parameters */ ! else if (strcmp(cmd, "pset") == 0) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1977,1997 ---- free(opt); } } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \pset -- set printing parameters ! */ ! static backslashResult ! exec_command_pset(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1267,1279 **** free(opt0); free(opt1); } ! /* \q or \quit */ ! else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) status = PSQL_CMD_TERMINATE; ! /* reset(clear) the buffer */ ! else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); --- 2031,2064 ---- free(opt0); free(opt1); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \q or \quit -- exit psql ! */ ! static backslashResult ! exec_command_quit(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) status = PSQL_CMD_TERMINATE; ! return status; ! } ! ! /* ! * \r -- reset (clear) the query buffer ! */ ! static backslashResult ! exec_command_reset(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf) ! { ! if (active_branch) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); *************** exec_command(const char *cmd, *** 1281,1288 **** puts(_("Query buffer reset (cleared).")); } ! /* \s save history in a file or show it on the screen */ ! else if (strcmp(cmd, "s") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2066,2083 ---- puts(_("Query buffer reset (cleared).")); } ! return PSQL_CMD_SKIP_LINE; ! } ! ! /* ! * \s -- save history in a file or show it on the screen ! */ ! static backslashResult ! exec_command_s(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1295,1303 **** putchar('\n'); free(fname); } ! /* \set -- generalized set variable/option command */ ! else if (strcmp(cmd, "set") == 0) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2090,2110 ---- putchar('\n'); free(fname); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \set -- set variable ! */ ! static backslashResult ! exec_command_set(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1336,1345 **** } free(opt0); } ! /* \setenv -- set environment command */ ! else if (strcmp(cmd, "setenv") == 0) { char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2143,2164 ---- } free(opt0); } + else + ignore_slash_options(scan_state); + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; + } ! /* ! * \setenv -- set environment variable ! */ ! static backslashResult ! exec_command_setenv(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1381,1389 **** free(envvar); free(envval); } ! /* \sf -- show a function's source code */ ! else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) { bool show_linenumbers = (strcmp(cmd, "sf+") == 0); PQExpBuffer func_buf; --- 2200,2220 ---- free(envvar); free(envval); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \sf -- show a function's source code ! */ ! static backslashResult ! exec_command_sf(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { bool show_linenumbers = (strcmp(cmd, "sf+") == 0); PQExpBuffer func_buf; *************** exec_command(const char *cmd, *** 1463,1471 **** free(func); destroyPQExpBuffer(func_buf); } ! /* \sv -- show a view's source code */ ! else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) { bool show_linenumbers = (strcmp(cmd, "sv+") == 0); PQExpBuffer view_buf; --- 2294,2314 ---- free(func); destroyPQExpBuffer(func_buf); } + else + ignore_slash_whole_line(scan_state); ! return status; ! } ! ! /* ! * \sv -- show a view's source code ! */ ! static backslashResult ! exec_command_sv(PsqlScanState scan_state, bool active_branch, const char *cmd) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { bool show_linenumbers = (strcmp(cmd, "sv+") == 0); PQExpBuffer view_buf; *************** exec_command(const char *cmd, *** 1539,1547 **** free(view); destroyPQExpBuffer(view_buf); } ! /* \t -- turn off headers and row count */ ! else if (strcmp(cmd, "t") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2382,2402 ---- free(view); destroyPQExpBuffer(view_buf); } + else + ignore_slash_whole_line(scan_state); ! return status; ! } ! ! /* ! * \t -- turn off table headers and row count ! */ ! static backslashResult ! exec_command_t(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1549,1557 **** success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } ! /* \T -- define html <table ...> attributes */ ! else if (strcmp(cmd, "T") == 0) { char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2404,2424 ---- success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \T -- define html <table ...> attributes ! */ ! static backslashResult ! exec_command_T(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1559,1567 **** success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } ! /* \timing -- toggle timing of queries */ ! else if (strcmp(cmd, "timing") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2426,2446 ---- success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \timing -- enable/disable timing of queries ! */ ! static backslashResult ! exec_command_timing(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1579,1587 **** } free(opt); } ! /* \unset */ ! else if (strcmp(cmd, "unset") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2458,2479 ---- } free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \unset -- unset variable ! */ ! static backslashResult ! exec_command_unset(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1596,1608 **** free(opt); } ! /* \w -- write query buffer to file */ ! else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) { FILE *fd = NULL; bool is_pipe = false; - char *fname = NULL; if (!query_buf) { --- 2488,2515 ---- free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \w -- write query buffer to file ! */ ! static backslashResult ! exec_command_write(PsqlScanState scan_state, bool active_branch, ! const char *cmd, ! PQExpBuffer query_buf, PQExpBuffer previous_buf) ! { ! backslashResult status = PSQL_CMD_SKIP_LINE; ! ! if (active_branch) { + char *fname = psql_scan_slash_option(scan_state, + OT_FILEPIPE, NULL, true); FILE *fd = NULL; bool is_pipe = false; if (!query_buf) { *************** exec_command(const char *cmd, *** 1611,1627 **** } else { - fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, true); - expand_tilde(&fname); - if (!fname) { psql_error("\\%s: missing required argument\n", cmd); ! success = false; } else { if (fname[0] == '|') { is_pipe = true; --- 2518,2531 ---- } else { if (!fname) { psql_error("\\%s: missing required argument\n", cmd); ! status = PSQL_CMD_ERROR; } else { + expand_tilde(&fname); if (fname[0] == '|') { is_pipe = true; *************** exec_command(const char *cmd, *** 1636,1642 **** if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); ! success = false; } } } --- 2540,2546 ---- if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); ! status = PSQL_CMD_ERROR; } } } *************** exec_command(const char *cmd, *** 1647,1652 **** --- 2551,2559 ---- if (query_buf && query_buf->len > 0) fprintf(fd, "%s\n", query_buf->data); + /* Applies to previous query if current buffer is empty */ + else if (previous_buf && previous_buf->len > 0) + fprintf(fd, "%s\n", previous_buf->data); if (is_pipe) result = pclose(fd); *************** exec_command(const char *cmd, *** 1656,1662 **** if (result == EOF) { psql_error("%s: %s\n", fname, strerror(errno)); ! success = false; } } --- 2563,2569 ---- if (result == EOF) { psql_error("%s: %s\n", fname, strerror(errno)); ! status = PSQL_CMD_ERROR; } } *************** exec_command(const char *cmd, *** 1665,1673 **** free(fname); } ! /* \watch -- execute a query every N seconds */ ! else if (strcmp(cmd, "watch") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2572,2593 ---- free(fname); } + else + ignore_slash_filepipe(scan_state); ! return status; ! } ! ! /* ! * \watch -- execute a query every N seconds ! */ ! static backslashResult ! exec_command_watch(PsqlScanState scan_state, bool active_branch, ! PQExpBuffer query_buf, PQExpBuffer previous_buf) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1682,1696 **** free(opt); } success = do_watch(query_buf, sleep); /* Reset the query buffer as though for \r */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } ! /* \x -- set or toggle expanded table representation */ ! else if (strcmp(cmd, "x") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2602,2631 ---- free(opt); } + /* Applies to previous query if current buffer is empty */ + copy_previous_query(query_buf, previous_buf); + success = do_watch(query_buf, sleep); /* Reset the query buffer as though for \r */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \x -- set or toggle expanded table representation ! */ ! static backslashResult ! exec_command_x(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1698,1706 **** success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } ! /* \z -- list table rights (equivalent to \dp) */ ! else if (strcmp(cmd, "z") == 0) { char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2633,2653 ---- success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \z -- list table privileges (equivalent to \dp) ! */ ! static backslashResult ! exec_command_z(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1709,1717 **** if (pattern) free(pattern); } ! /* \! -- shell escape */ ! else if (strcmp(cmd, "!") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 2656,2676 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \! -- execute shell command ! */ ! static backslashResult ! exec_command_shell_escape(PsqlScanState scan_state, bool active_branch) ! { ! bool success = true; ! ! if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); *************** exec_command(const char *cmd, *** 1719,1727 **** success = do_shell(opt); free(opt); } ! /* \? -- slash command help */ ! else if (strcmp(cmd, "?") == 0) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2678,2696 ---- success = do_shell(opt); free(opt); } + else + ignore_slash_whole_line(scan_state); ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } ! ! /* ! * \? -- print help about backslash commands ! */ ! static backslashResult ! exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1734,1767 **** helpVariables(pset.popt.topt.pager); else slashUsage(pset.popt.topt.pager); } ! #if 0 /* ! * These commands don't do anything. I just use them to test the parser. */ ! else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0) ! { ! int i = 0; ! char *value; ! while ((value = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, true))) ! { ! psql_error("+ opt(%d) = |%s|\n", i++, value); ! free(value); ! } } - #endif ! else ! status = PSQL_CMD_UNKNOWN; ! if (!success) ! status = PSQL_CMD_ERROR; ! return status; } /* --- 2703,2933 ---- helpVariables(pset.popt.topt.pager); else slashUsage(pset.popt.topt.pager); + + if (opt0) + free(opt0); } + else + ignore_slash_options(scan_state); + + return PSQL_CMD_SKIP_LINE; + } ! ! /* ! * Read and interpret an argument to the \connect slash command. ! */ ! static char * ! read_connect_arg(PsqlScanState scan_state) ! { ! char *result; ! char quote; /* ! * Ideally we should treat the arguments as SQL identifiers. But for ! * backwards compatibility with 7.2 and older pg_dump files, we have to ! * take unquoted arguments verbatim (don't downcase them). For now, ! * double-quoted arguments may be stripped of double quotes (as if SQL ! * identifiers). By 7.4 or so, pg_dump files can be expected to ! * double-quote all mixed-case \connect arguments, and then we can get rid ! * of OT_SQLIDHACK. */ ! result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true); ! if (!result) ! return NULL; ! ! if (quote) ! return result; ! ! if (*result == '\0' || strcmp(result, "-") == 0) ! return NULL; ! ! return result; ! } ! ! /* ! * Read a boolean expression, return it as a PQExpBuffer string. ! * ! * Note: anything more or less than one token will certainly fail to be ! * parsed by ParseVariableBool, so we don't worry about complaining here. ! * This routine's return data structure will need to be rethought anyway ! * to support likely future extensions such as "\if defined VARNAME". ! */ ! static PQExpBuffer ! gather_boolean_expression(PsqlScanState scan_state) ! { ! PQExpBuffer exp_buf = createPQExpBuffer(); ! int num_options = 0; ! char *value; ! ! /* collect all arguments for the conditional command into exp_buf */ ! while ((value = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false)) != NULL) ! { ! /* add spaces between tokens */ ! if (num_options > 0) ! appendPQExpBufferChar(exp_buf, ' '); ! appendPQExpBufferStr(exp_buf, value); ! num_options++; ! free(value); } ! return exp_buf; ! } ! /* ! * Read a boolean expression, return true if the expression ! * was a valid boolean expression that evaluated to true. ! * Otherwise return false. ! * ! * Note: conditional stack's top state must be active, else lexer will ! * fail to expand variables and backticks. ! */ ! static bool ! is_true_boolean_expression(PsqlScanState scan_state, const char *name) ! { ! PQExpBuffer buf = gather_boolean_expression(scan_state); ! bool value = false; ! bool success = ParseVariableBool(buf->data, name, &value); ! destroyPQExpBuffer(buf); ! return success && value; ! } ! ! /* ! * Read a boolean expression, but do nothing with it. ! * ! * Note: conditional stack's top state must be INACTIVE, else lexer will ! * expand variables and backticks, which we do not want here. ! */ ! static void ! ignore_boolean_expression(PsqlScanState scan_state) ! { ! PQExpBuffer buf = gather_boolean_expression(scan_state); ! ! destroyPQExpBuffer(buf); ! } ! ! /* ! * Read and discard "normal" slash command options. ! * ! * This should be used for inactive-branch processing of any slash command ! * that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters. ! * We don't need to worry about exactly how many it would eat, since the ! * cleanup logic in HandleSlashCmds would silently discard any extras anyway. ! */ ! static void ! ignore_slash_options(PsqlScanState scan_state) ! { ! char *arg; ! ! while ((arg = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false)) != NULL) ! free(arg); ! } ! ! /* ! * Read and discard FILEPIPE slash command argument. ! * ! * This *MUST* be used for inactive-branch processing of any slash command ! * that takes an OT_FILEPIPE option. Otherwise we might consume a different ! * amount of option text in active and inactive cases. ! */ ! static void ! ignore_slash_filepipe(PsqlScanState scan_state) ! { ! char *arg = psql_scan_slash_option(scan_state, ! OT_FILEPIPE, NULL, false); ! ! if (arg) ! free(arg); ! } ! ! /* ! * Read and discard whole-line slash command argument. ! * ! * This *MUST* be used for inactive-branch processing of any slash command ! * that takes an OT_WHOLE_LINE option. Otherwise we might consume a different ! * amount of option text in active and inactive cases. ! */ ! static void ! ignore_slash_whole_line(PsqlScanState scan_state) ! { ! char *arg = psql_scan_slash_option(scan_state, ! OT_WHOLE_LINE, NULL, false); ! ! if (arg) ! free(arg); ! } ! ! /* ! * 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); ! } ! ! /* ! * Prepare to possibly restore query buffer to its current state ! * (cf. discard_query_text). ! * ! * We need to remember the length of the query buffer, and the lexer's ! * notion of the parenthesis nesting depth. ! */ ! static void ! save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! if (query_buf) ! conditional_stack_set_query_len(cstack, query_buf->len); ! conditional_stack_set_paren_depth(cstack, ! psql_scan_get_paren_depth(scan_state)); ! } ! ! /* ! * Discard any query text absorbed during an inactive conditional branch. ! * ! * We must discard data that was appended to query_buf during an inactive ! * \if branch. We don't have to do anything there if there's no query_buf. ! * ! * Also, reset the lexer state to the same paren depth there was before. ! * (The rest of its state doesn't need attention, since we could not be ! * inside a comment or literal or partial token.) ! */ ! static void ! discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! if (query_buf) ! { ! int new_len = conditional_stack_get_query_len(cstack); ! ! Assert(new_len >= 0 && new_len <= query_buf->len); ! query_buf->len = new_len; ! query_buf->data[new_len] = '\0'; ! } ! psql_scan_set_paren_depth(scan_state, ! conditional_stack_get_paren_depth(cstack)); ! } ! ! /* ! * If query_buf is empty, copy previous_buf into it. ! * ! * This is used by various slash commands for which re-execution of a ! * previous query is a common usage. For convenience, we allow the ! * case of query_buf == NULL (and do nothing). ! */ ! static void ! copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf) ! { ! if (query_buf && query_buf->len == 0) ! appendPQExpBufferStr(query_buf, previous_buf->data); } /* diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h index d0c3264..e8ea847 100644 *** a/src/bin/psql/command.h --- b/src/bin/psql/command.h *************** *** 10,15 **** --- 10,16 ---- #include "fe_utils/print.h" #include "fe_utils/psqlscan.h" + #include "conditional.h" typedef enum _backslashResult *************** typedef enum _backslashResult *** 25,31 **** extern backslashResult HandleSlashCmds(PsqlScanState scan_state, ! PQExpBuffer query_buf); extern int process_file(char *filename, bool use_relative_path); --- 26,34 ---- extern backslashResult HandleSlashCmds(PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf); extern int process_file(char *filename, bool use_relative_path); diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index e9d4fe6..b06ae97 100644 *** a/src/bin/psql/common.c --- b/src/bin/psql/common.c *************** setQFout(const char *fname) *** 121,127 **** * (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. */ char * psql_get_variable(const char *varname, bool escape, bool as_ident, --- 121,128 ---- * (Failure in escaping should lead to returning NULL.) * * "passthrough" is the pointer previously given to psql_scan_set_passthrough. ! * In psql, passthrough points to a ConditionalStack, which we check to ! * determine whether variable expansion is allowed. */ char * psql_get_variable(const char *varname, bool escape, bool as_ident, *************** psql_get_variable(const char *varname, b *** 130,135 **** --- 131,140 ---- char *result; const char *value; + /* In an inactive \if branch, suppress all variable substitutions */ + if (passthrough && !conditional_active((ConditionalStack) passthrough)) + return NULL; + value = GetVariable(pset.vars, varname); if (!value) return NULL; diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c index ...63977ce . *** a/src/bin/psql/conditional.c --- b/src/bin/psql/conditional.c *************** *** 0 **** --- 1,153 ---- + /* + * 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->query_len = -1; + p->paren_depth = -1; + 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 we should execute commands normally; that is, the current + * conditional branch is active, or there is no open \if block. + */ + bool + conditional_active(ConditionalStack cstack) + { + ifState s = conditional_stack_peek(cstack); + + return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE; + } + + /* + * Save current query buffer length in topmost stack entry. + */ + void + conditional_stack_set_query_len(ConditionalStack cstack, int len) + { + Assert(!conditional_stack_empty(cstack)); + cstack->head->query_len = len; + } + + /* + * Fetch last-recorded query buffer length from topmost stack entry. + * Will return -1 if no stack or it was never saved. + */ + int + conditional_stack_get_query_len(ConditionalStack cstack) + { + if (conditional_stack_empty(cstack)) + return -1; + return cstack->head->query_len; + } + + /* + * Save current parenthesis nesting depth in topmost stack entry. + */ + void + conditional_stack_set_paren_depth(ConditionalStack cstack, int depth) + { + Assert(!conditional_stack_empty(cstack)); + cstack->head->paren_depth = depth; + } + + /* + * Fetch last-recorded parenthesis nesting depth from topmost stack entry. + * Will return -1 if no stack or it was never saved. + */ + int + conditional_stack_get_paren_depth(ConditionalStack cstack) + { + if (conditional_stack_empty(cstack)) + return -1; + return cstack->head->paren_depth; + } diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h index ...90e4d93 . *** a/src/bin/psql/conditional.h --- b/src/bin/psql/conditional.h *************** *** 0 **** --- 1,83 ---- + /* + * 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 that is true + * and all parent branches (if any) are true */ + IFSTATE_FALSE, /* currently in an \if or \elif that is false + * but no true branch has yet been seen, and + * all parent branches (if any) are true */ + IFSTATE_IGNORED, /* currently in an \elif that follows a true + * branch, or the whole \if is a child of a + * false parent branch */ + IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all + * parent branches (if any) are true */ + IFSTATE_ELSE_FALSE /* currently in an \else that is false or + * ignored */ + } ifState; + + /* + * The state of nested \ifs is stored in a stack. + * + * query_len is used to determine what accumulated text to throw away at the + * end of an inactive branch. (We could, perhaps, teach the lexer to not add + * stuff to the query buffer in the first place when inside an inactive branch; + * but that would be very invasive.) We also need to save and restore the + * lexer's parenthesis nesting depth when throwing away text. (We don't need + * to save and restore any of its other state, such as comment nesting depth, + * because a backslash command could never appear inside a comment or SQL + * literal.) + */ + typedef struct IfStackElem + { + ifState if_state; /* current state, see enum above */ + int query_len; /* length of query_buf at last branch start */ + int paren_depth; /* parenthesis depth at last branch start */ + struct IfStackElem *next; /* next surrounding \if, if any */ + } 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 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_stack_empty(ConditionalStack cstack); + + extern bool conditional_active(ConditionalStack cstack); + + extern void conditional_stack_set_query_len(ConditionalStack cstack, int len); + + extern int conditional_stack_get_query_len(ConditionalStack cstack); + + extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth); + + extern int conditional_stack_get_paren_depth(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 *************** handleCopyIn(PGconn *conn, FILE *copystr *** 552,558 **** /* interactive input probably silly, but give one prompt anyway */ if (showprompt) { ! const char *prompt = get_prompt(PROMPT_COPY); fputs(prompt, stdout); fflush(stdout); --- 552,558 ---- /* interactive input probably silly, but give one prompt anyway */ if (showprompt) { ! const char *prompt = get_prompt(PROMPT_COPY, NULL); fputs(prompt, stdout); fflush(stdout); *************** handleCopyIn(PGconn *conn, FILE *copystr *** 590,596 **** if (showprompt) { ! const char *prompt = get_prompt(PROMPT_COPY); fputs(prompt, stdout); fflush(stdout); --- 590,596 ---- if (showprompt) { ! 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..ac43522 100644 *** a/src/bin/psql/help.c --- b/src/bin/psql/help.c *************** slashUsage(unsigned short int pager) *** 167,173 **** * Use "psql --help=commands | wc" to count correctly. It's okay to count * the USE_READLINE line even in builds without that. */ ! output = PageOutput(113, pager ? &(pset.popt.topt) : NULL); fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n")); --- 167,173 ---- * Use "psql --help=commands | wc" to count correctly. It's okay to count * the USE_READLINE line even in builds without that. */ ! output = PageOutput(122, pager ? &(pset.popt.topt) : NULL); fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n")); *************** slashUsage(unsigned short int pager) *** 210,215 **** --- 210,222 ---- fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n")); fprintf(output, "\n"); + fprintf(output, _("Conditional\n")); + fprintf(output, _(" \\if EXPR begin conditional block\n")); + fprintf(output, _(" \\elif EXPR alternative within current conditional block\n")); + fprintf(output, _(" \\else final alternative within current conditional block\n")); + fprintf(output, _(" \\endif end 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..2bc2f43 100644 *** a/src/bin/psql/mainloop.c --- b/src/bin/psql/mainloop.c *************** int *** 35,40 **** --- 35,41 ---- 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, *************** MainLoop(FILE *source) *** 50,65 **** volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; - - /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; ! /* Save old settings */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* Establish new source */ pset.cur_cmd_source = source; --- 51,65 ---- volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; ! /* Save the prior command source */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; + /* pset.stmt_lineno does not need to be saved and restored */ /* Establish new source */ pset.cur_cmd_source = source; *************** MainLoop(FILE *source) *** 69,74 **** --- 69,76 ---- /* 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(); *************** MainLoop(FILE *source) *** 122,128 **** --- 124,142 ---- cancel_pressed = false; if (pset.cur_cmd_interactive) + { putc('\n', stdout); + + /* + * if interactive user is in an \if block, then Ctrl-C will + * exit from the innermost \if. + */ + if (!conditional_stack_empty(cond_stack)) + { + psql_error("\\if: escaped\n"); + conditional_stack_pop(cond_stack); + } + } else { successResult = EXIT_USER; *************** MainLoop(FILE *source) *** 140,146 **** /* 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); } else { --- 154,161 ---- /* 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, cond_stack), ! query_buf); } else { *************** MainLoop(FILE *source) *** 286,293 **** (scan_result == PSCAN_EOL && pset.singleline)) { /* ! * Save query in history. We use history_buf to accumulate ! * multi-line queries into a single history entry. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { --- 301,310 ---- (scan_result == PSCAN_EOL && pset.singleline)) { /* ! * Save line in history. We use history_buf to accumulate ! * multi-line queries into a single history entry. Note that ! * history accumulation works on input lines, so it doesn't ! * matter whether the query will be ignored due to \if. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { *************** MainLoop(FILE *source) *** 296,317 **** line_saved_in_history = true; } ! /* execute query */ ! success = SendQuery(query_buf->data); ! slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; ! pset.stmt_lineno = 1; ! ! /* transfer query to previous_buf by pointer-swapping */ { ! PQExpBuffer swap_buf = previous_buf; ! previous_buf = query_buf; ! query_buf = swap_buf; ! } ! resetPQExpBuffer(query_buf); ! added_nl_pos = -1; ! /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { --- 313,348 ---- line_saved_in_history = true; } ! /* execute query unless we're in an inactive \if branch */ ! if (conditional_active(cond_stack)) { ! success = SendQuery(query_buf->data); ! slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; ! pset.stmt_lineno = 1; ! /* transfer query to previous_buf by pointer-swapping */ ! { ! PQExpBuffer swap_buf = previous_buf; ! previous_buf = query_buf; ! query_buf = swap_buf; ! } ! resetPQExpBuffer(query_buf); ! ! added_nl_pos = -1; ! /* we need not do psql_scan_reset() here */ ! } ! else ! { ! /* if interactive, warn about non-executed query */ ! if (pset.cur_cmd_interactive) ! psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); ! /* fake an OK result for purposes of loop checks */ ! success = true; ! slashCmdStatus = PSQL_CMD_SEND; ! pset.stmt_lineno = 1; ! /* note that query_buf doesn't change state */ ! } } else if (scan_result == PSCAN_BACKSLASH) { *************** MainLoop(FILE *source) *** 343,363 **** /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, ! query_buf->len > 0 ? ! query_buf : previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; - pset.stmt_lineno = 1; ! if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) && ! query_buf->len == 0) ! { ! /* copy previous buffer to current for handling */ ! appendPQExpBufferStr(query_buf, previous_buf->data); ! } if (slashCmdStatus == PSQL_CMD_SEND) { success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ --- 374,397 ---- /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, ! cond_stack, ! query_buf, ! previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; ! /* ! * Resetting stmt_lineno after a backslash command isn't ! * always appropriate, but it's what we've done historically ! * and there have been few complaints. ! */ ! pset.stmt_lineno = 1; if (slashCmdStatus == PSQL_CMD_SEND) { + /* should not see this in inactive branch */ + Assert(conditional_active(cond_stack)); + success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ *************** MainLoop(FILE *source) *** 374,379 **** --- 408,415 ---- } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { + /* should not see this in inactive branch */ + Assert(conditional_active(cond_stack)); /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); *************** MainLoop(FILE *source) *** 429,436 **** if (pset.cur_cmd_interactive) pg_send_history(history_buf); ! /* execute query */ ! success = SendQuery(query_buf->data); if (!success && die_on_error) successResult = EXIT_USER; --- 465,481 ---- if (pset.cur_cmd_interactive) pg_send_history(history_buf); ! /* execute query unless we're in an inactive \if branch */ ! if (conditional_active(cond_stack)) ! { ! success = SendQuery(query_buf->data); ! } ! else ! { ! if (pset.cur_cmd_interactive) ! psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); ! success = true; ! } if (!success && die_on_error) successResult = EXIT_USER; *************** MainLoop(FILE *source) *** 439,444 **** --- 484,502 ---- } /* + * 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 *************** MainLoop(FILE *source) *** 452,457 **** --- 510,516 ---- 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,72 **** */ char * ! get_prompt(promptStatus_t status) { #define MAX_PROMPT_SIZE 256 static char destination[MAX_PROMPT_SIZE + 1]; --- 66,72 ---- */ char * ! get_prompt(promptStatus_t status, ConditionalStack cstack) { #define MAX_PROMPT_SIZE 256 static char destination[MAX_PROMPT_SIZE + 1]; *************** get_prompt(promptStatus_t status) *** 188,194 **** switch (status) { case PROMPT_READY: ! if (!pset.db) buf[0] = '!'; else if (!pset.singleline) buf[0] = '='; --- 188,196 ---- switch (status) { case PROMPT_READY: ! 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,16 **** /* enum promptStatus_t is now defined by psqlscan.h */ #include "fe_utils/psqlscan.h" ! char *get_prompt(promptStatus_t status); #endif /* PROMPT_H */ --- 10,17 ---- /* enum promptStatus_t is now defined by psqlscan.h */ #include "fe_utils/psqlscan.h" + #include "conditional.h" ! char *get_prompt(promptStatus_t status, ConditionalStack cstack); #endif /* PROMPT_H */ diff --git a/src/bin/psql/psqlscanslash.h b/src/bin/psql/psqlscanslash.h index 266e93a..db76061 100644 *** a/src/bin/psql/psqlscanslash.h --- b/src/bin/psql/psqlscanslash.h *************** enum slash_option_type *** 18,25 **** OT_SQLID, /* treat as SQL identifier */ OT_SQLIDHACK, /* SQL identifier, but don't downcase */ OT_FILEPIPE, /* it's a filename or pipe */ ! OT_WHOLE_LINE, /* just snarf the rest of the line */ ! OT_NO_EVAL /* no expansion of backticks or variables */ }; --- 18,24 ---- OT_SQLID, /* treat as SQL identifier */ OT_SQLIDHACK, /* SQL identifier, but don't downcase */ OT_FILEPIPE, /* it's a filename or pipe */ ! OT_WHOLE_LINE /* just snarf the rest of the line */ }; *************** extern char *psql_scan_slash_option(Psql *** 32,37 **** --- 31,40 ---- extern void psql_scan_slash_command_end(PsqlScanState state); + extern int psql_scan_get_paren_depth(PsqlScanState state); + + extern void psql_scan_set_paren_depth(PsqlScanState state, int depth); + extern void dequote_downcase_identifier(char *str, bool downcase, int encoding); #endif /* PSQLSCANSLASH_H */ diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index ba4a08d..319afdc 100644 *** a/src/bin/psql/psqlscanslash.l --- b/src/bin/psql/psqlscanslash.l *************** *** 19,24 **** --- 19,25 ---- #include "postgres_fe.h" #include "psqlscanslash.h" + #include "conditional.h" #include "libpq-fe.h" } *************** other . *** 230,237 **** :{variable_char}+ { /* Possible psql variable substitution */ ! if (option_type == OT_NO_EVAL || ! cur_state->callbacks->get_variable == NULL) ECHO; else { --- 231,237 ---- :{variable_char}+ { /* Possible psql variable substitution */ ! if (cur_state->callbacks->get_variable == NULL) ECHO; else { *************** other . *** 268,292 **** } :'{variable_char}+' { ! if (option_type == OT_NO_EVAL) ! ECHO; ! else ! { ! psqlscan_escape_variable(cur_state, yytext, yyleng, false); ! *option_quote = ':'; ! } unquoted_option_chars = 0; } :\"{variable_char}+\" { ! if (option_type == OT_NO_EVAL) ! ECHO; ! else ! { ! psqlscan_escape_variable(cur_state, yytext, yyleng, true); ! *option_quote = ':'; ! } unquoted_option_chars = 0; } --- 268,282 ---- } :'{variable_char}+' { ! psqlscan_escape_variable(cur_state, yytext, yyleng, false); ! *option_quote = ':'; unquoted_option_chars = 0; } :\"{variable_char}+\" { ! psqlscan_escape_variable(cur_state, yytext, yyleng, true); ! *option_quote = ':'; unquoted_option_chars = 0; } *************** other . *** 353,360 **** */ "`" { ! /* In NO_EVAL mode, don't evaluate the command */ ! if (option_type != OT_NO_EVAL) evaluate_backtick(cur_state); BEGIN(xslasharg); } --- 343,351 ---- */ "`" { ! /* In an inactive \if branch, don't evaluate the command */ ! if (cur_state->cb_passthrough == NULL || ! conditional_active((ConditionalStack) cur_state->cb_passthrough)) evaluate_backtick(cur_state); BEGIN(xslasharg); } *************** psql_scan_slash_command_end(PsqlScanStat *** 642,647 **** --- 633,657 ---- } /* + * Fetch current paren nesting depth + */ + int + psql_scan_get_paren_depth(PsqlScanState state) + { + return state->paren_depth; + } + + /* + * Set paren nesting depth + */ + void + psql_scan_set_paren_depth(PsqlScanState state, int depth) + { + Assert(depth >= 0); + state->paren_depth = depth; + } + + /* * De-quote and optionally downcase a SQL identifier. * * The string at *str is modified in-place; it can become shorter, diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 694f0ef..8068a28 100644 *** a/src/bin/psql/startup.c --- b/src/bin/psql/startup.c *************** main(int argc, char *argv[]) *** 331,336 **** --- 331,337 ---- else if (cell->action == ACT_SINGLE_SLASH) { PsqlScanState scan_state; + ConditionalStack cond_stack; if (pset.echo == PSQL_ECHO_ALL) puts(cell->val); *************** main(int argc, char *argv[]) *** 339,349 **** psql_scan_setup(scan_state, cell->val, strlen(cell->val), pset.encoding, standard_strings()); ! successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; psql_scan_destroy(scan_state); } else if (cell->action == ACT_FILE) { --- 340,356 ---- 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, ! cond_stack, ! NULL, ! 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..50251c3 100644 *** a/src/test/regress/expected/psql.out --- b/src/test/regress/expected/psql.out *************** deallocate q; *** 2735,2740 **** --- 2735,2907 ---- \pset format aligned \pset expanded off \pset border 1 + -- tests for \if ... \endif + \if true + select 'okay'; + ?column? + ---------- + okay + (1 row) + + select 'still okay'; + ?column? + ------------ + still okay + (1 row) + + \else + not okay; + still not okay + \endif + -- at this point query buffer should still have last valid line + \g + ?column? + ------------ + still okay + (1 row) + + -- \if should work okay on part of a query + select + \if true + 42 + \else + (bogus + \endif + forty_two; + forty_two + ----------- + 42 + (1 row) + + select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; + forty_two + ----------- + 42 + (1 row) + + -- 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 expression": 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 and backticks are not expanded when ignoring extra args + \set foo bar + \echo :foo :'foo' :"foo" + bar 'bar' "bar" + \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo" + \pset: extra argument "nosuchcommand" ignored + \pset: extra argument ":foo" ignored + \pset: extra argument ":'foo'" ignored + \pset: extra argument ":"foo"" ignored + -- show that vars and backticks are not expanded and commands are ignored + -- when in a false if-branch + \set try_to_quit '\\q' + \if false + :try_to_quit + \echo `nosuchcommand` :foo :'foo' :"foo" + \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo" + \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 + -- \endif here is eaten as part of whole-line argument + \! whole_line \endif + \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..d9ab508 100644 *** a/src/test/regress/sql/psql.sql --- b/src/test/regress/sql/psql.sql *************** deallocate q; *** 382,387 **** --- 382,529 ---- \pset expanded off \pset border 1 + -- tests for \if ... \endif + + \if true + select 'okay'; + select 'still okay'; + \else + not okay; + still not okay + \endif + + -- at this point query buffer should still have last valid line + \g + + -- \if should work okay on part of a query + select + \if true + 42 + \else + (bogus + \endif + forty_two; + + select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; + + -- 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 and backticks are not expanded when ignoring extra args + \set foo bar + \echo :foo :'foo' :"foo" + \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo" + + -- show that vars and backticks are not expanded and commands are ignored + -- when in a false if-branch + \set try_to_quit '\\q' + \if false + :try_to_quit + \echo `nosuchcommand` :foo :'foo' :"foo" + \pset fieldsep | `nosuchcommand` :foo :'foo' :"foo" + \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 + -- \endif here is eaten as part of whole-line argument + \! whole_line \endif + \else + \echo 'should print #8-1' + \endif + -- SHOW_CONTEXT \set SHOW_CONTEXT never
pgsql-hackers by date: