Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ctRVm-0003a7-Up for pgsql-hackers@arkaria.postgresql.org; Thu, 30 Mar 2017 04:16:39 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84_2) (envelope-from ) id 1ctRVm-0007mi-C2 for pgsql-hackers@arkaria.postgresql.org; Thu, 30 Mar 2017 04:16:38 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1ctRVY-0007XF-H4; Thu, 30 Mar 2017 04:16:24 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1ctRVR-0000yK-Ah; Thu, 30 Mar 2017 04:16:24 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.14.4/8.14.4) with ESMTP id v2U4FxwJ010991; Thu, 30 Mar 2017 00:15:59 -0400 From: Tom Lane To: Fabien COELHO cc: Corey Huinker , Daniel Verite , "David G. Johnston" , Greg Stark , Erik Rijkers , Robert Haas , Jim Nasby , PostgreSQL , pgsql-hackers-owner@postgresql.org Subject: Re: \if, \elseif, \else, \endif (was Re: PSQL commands: \quit_if, \quit_unless) In-reply-to: References: <24980.1490748720@sss.pgh.pa.us> <21747.1490796899@sss.pgh.pa.us> Comments: In-reply-to Fabien COELHO message dated "Wed, 29 Mar 2017 22:51:41 +0200" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <10945.1490847311.0@sss.pgh.pa.us> Date: Thu, 30 Mar 2017 00:15:59 -0400 Message-ID: <10990.1490847359@sss.pgh.pa.us> X-Pg-Spam-Score: -1.9 (-) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <10945.1490847311.1@sss.pgh.pa.us> Fabien COELHO 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 ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="psql-if-v30.patch"; charset="us-ascii" Content-ID: <10945.1490847311.2@sss.pgh.pa.us> Content-Description: psql-if-v30.patch Content-Transfer-Encoding: quoted-printable 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 ---- =20=20 =20=20 + \if exp= ression + \elif e= xpression + \else + \endif + + + This group of commands implements nestable conditional blocks. + A conditional block must begin with an \if and= end + with an \endif. In between there may be any n= umber + of \elif clauses, which may optionally be foll= owed + by a single \else clause. Ordinary queries and + other types of backslash commands may (and usually do) appear bet= ween + the commands forming a conditional block. + + + The \if and \elif commands = read + their argument(s) and evaluate them as a boolean expression. If = the + expression yields true then processing continu= es + normally; otherwise, lines are skipped until a + matching \elif, \else, + or \endif is reached. Once + an \if or \elif test has + succeeded, the arguments of later \elif comman= ds in + the same block are not evaluated but are treated as false. Lines + following an \else are processed only if no ea= rlier + matching \if or \elif succe= eded. + + + The expression arg= ument + of an \if or \elif command + is subject to variable interpolation and backquote expansion, just + like any other backslash command argument. After that it is eval= uated + like the value of an on/off option variable. So a valid value + is any unambiguous case-insensitive match for one of: + true, false, 1, + 0, on, off, + yes, no. For example, + t, T, and tR + will all be considered to be true. + + + Expressions that do not properly evaluate to true or false will + generate a warning and be treated as false. + + + Lines being skipped are parsed normally to identify queries and + backslash commands, but queries are not sent to the server, and + backslash commands other than conditionals + (\if, \elif, + \else, \endif) are + ignored. Conditional commands are checked only for valid nesting. + Variable references in skipped lines are not expanded, and backqu= ote + expansion is not performed either. + + + All the backslash commands of a given conditional block must appe= ar in + the same source file. If EOF is reached on the main input file or= an + \include-ed file before all local + \if-blocks have been closed, + then psql will raise an error. + + + Here is an example: + + + -- check for the existence of two separate records in the database and st= ore + -- the results in separate psql variables + SELECT + EXISTS(SELECT 1 FROM customer WHERE customer_id =3D 123) as is_custom= er, + EXISTS(SELECT 1 FROM employee WHERE employee_id =3D 456) as is_employ= ee + \gset + \if :is_customer + SELECT * FROM customer WHERE customer_id =3D 123; + \elif :is_employee + \echo 'is not a customer but is an employee' + SELECT * FROM employee WHERE employee_id =3D 456; + \else + \if yes + \echo 'not a customer or employee' + \else + \echo 'this will never print' + \endif + \endif + + + +=20 +=20 + \l[+] or \list[+] [ pattern ] *************** testdb=3D> INSERT INTO my_ta *** 3715,3721 **** In prompt 1 normally =3D, ! but ^ if in single-line mode, or ! if the session is disconnected from the database (which can happen if \connect fails). In prompt 2 %R is replaced by a character that --- 3804,3811 ---- In prompt 1 normally =3D, ! but @ if the session is in an inactive branch = of a ! conditional block, or ^ if in single-line mode, or ! if the session is disconnected from the database (which can happen if \connect fails). In prompt 2 %R is replaced by a character that diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile index f8e31ea..ab2cfa6 100644 *** a/src/bin/psql/Makefile --- b/src/bin/psql/Makefile *************** REFDOCDIR=3D $(top_srcdir)/doc/src/sgml/re *** 21,30 **** override CPPFLAGS :=3D -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) LDFLAGS +=3D -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq =20=20 ! OBJS=3D 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) =20=20 =20=20 --- 21,30 ---- override CPPFLAGS :=3D -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) LDFLAGS +=3D -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq =20=20 ! OBJS=3D 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) =20=20 =20=20 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; =20=20 ! /* 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 *de= sc, --- 56,158 ---- EditableView } EditableObjectType; =20=20 ! /* 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 acti= ve_branch); ! static backslashResult exec_command_C(PsqlScanState scan_state, bool acti= ve_branch); ! static backslashResult exec_command_connect(PsqlScanState scan_state, boo= l active_branch); ! static backslashResult exec_command_cd(PsqlScanState scan_state, bool act= ive_branch, ! const char *cmd); ! static backslashResult exec_command_conninfo(PsqlScanState scan_state, bo= ol active_branch); ! static backslashResult exec_command_copy(PsqlScanState scan_state, bool a= ctive_branch); ! static backslashResult exec_command_copyright(PsqlScanState scan_state, b= ool active_branch); ! static backslashResult exec_command_crosstabview(PsqlScanState scan_state= , bool active_branch); ! static backslashResult exec_command_d(PsqlScanState scan_state, bool acti= ve_branch, ! const char *cmd); ! static backslashResult exec_command_edit(PsqlScanState scan_state, bool a= ctive_branch, ! PQExpBuffer query_buf, PQExpBuffer previous_buf); ! static backslashResult exec_command_ef(PsqlScanState scan_state, bool act= ive_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_ev(PsqlScanState scan_state, bool act= ive_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_echo(PsqlScanState scan_state, bool a= ctive_branch, ! const char *cmd); ! static backslashResult exec_command_elif(PsqlScanState scan_state, Condit= ionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_else(PsqlScanState scan_state, Condit= ionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_endif(PsqlScanState scan_state, Condi= tionalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_encoding(PsqlScanState scan_state, bo= ol active_branch); ! static backslashResult exec_command_errverbose(PsqlScanState scan_state, = bool active_branch); ! static backslashResult exec_command_f(PsqlScanState scan_state, bool acti= ve_branch); ! static backslashResult exec_command_g(PsqlScanState scan_state, bool acti= ve_branch, ! const char *cmd); ! static backslashResult exec_command_gexec(PsqlScanState scan_state, bool = active_branch); ! static backslashResult exec_command_gset(PsqlScanState scan_state, bool a= ctive_branch); ! static backslashResult exec_command_help(PsqlScanState scan_state, bool a= ctive_branch); ! static backslashResult exec_command_html(PsqlScanState scan_state, bool a= ctive_branch); ! static backslashResult exec_command_include(PsqlScanState scan_state, boo= l active_branch, ! const char *cmd); ! static backslashResult exec_command_if(PsqlScanState scan_state, Conditio= nalStack cstack, ! PQExpBuffer query_buf); ! static backslashResult exec_command_list(PsqlScanState scan_state, bool a= ctive_branch, ! const char *cmd); ! static backslashResult exec_command_lo(PsqlScanState scan_state, bool act= ive_branch, ! const char *cmd); ! static backslashResult exec_command_out(PsqlScanState scan_state, bool ac= tive_branch); ! static backslashResult exec_command_print(PsqlScanState scan_state, bool = active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_password(PsqlScanState scan_state, bo= ol 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 a= ctive_branch); ! static backslashResult exec_command_quit(PsqlScanState scan_state, bool a= ctive_branch); ! static backslashResult exec_command_reset(PsqlScanState scan_state, bool = active_branch, ! PQExpBuffer query_buf); ! static backslashResult exec_command_s(PsqlScanState scan_state, bool acti= ve_branch); ! static backslashResult exec_command_set(PsqlScanState scan_state, bool ac= tive_branch); ! static backslashResult exec_command_setenv(PsqlScanState scan_state, bool= active_branch, ! const char *cmd); ! static backslashResult exec_command_sf(PsqlScanState scan_state, bool act= ive_branch, ! const char *cmd); ! static backslashResult exec_command_sv(PsqlScanState scan_state, bool act= ive_branch, ! const char *cmd); ! static backslashResult exec_command_t(PsqlScanState scan_state, bool acti= ve_branch); ! static backslashResult exec_command_T(PsqlScanState scan_state, bool acti= ve_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 acti= ve_branch); ! static backslashResult exec_command_z(PsqlScanState scan_state, bool acti= ve_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 ch= ar *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, ConditionalSt= ack 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 previo= us_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 *de= sc, *************** 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 **** =20=20 backslashResult HandleSlashCmds(PsqlScanState scan_state, ! PQExpBuffer query_buf) { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; char *cmd; char *arg; =20=20 Assert(scan_state !=3D NULL); =20=20 /* Parse off the command name */ cmd =3D psql_scan_slash_command(scan_state); =20=20 /* And try to execute it */ ! status =3D exec_command(cmd, scan_state, query_buf); =20=20 if (status =3D=3D PSQL_CMD_UNKNOWN) { --- 204,225 ---- =20=20 backslashResult HandleSlashCmds(PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf) { ! backslashResult status; char *cmd; char *arg; =20=20 Assert(scan_state !=3D NULL); + Assert(cstack !=3D NULL); =20=20 /* Parse off the command name */ cmd =3D psql_scan_slash_command(scan_state); =20=20 /* And try to execute it */ ! status =3D exec_command(cmd, scan_state, cstack, query_buf, previous_buf= ); =20=20 if (status =3D=3D PSQL_CMD_UNKNOWN) { *************** HandleSlashCmds(PsqlScanState scan_state *** 131,144 **** =20=20 if (status !=3D PSQL_CMD_ERROR) { ! /* eat any remaining arguments after a valid command */ ! /* note we suppress evaluation of backticks here */ while ((arg =3D 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 ---- =20=20 if (status !=3D 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 =3D conditional_active(cstack); !=20 ! conditional_stack_push(cstack, IFSTATE_IGNORED); while ((arg =3D 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; } =20=20 /* ! * Read and interpret an argument to the \connect slash command. */ ! static char * ! read_connect_arg(PsqlScanState scan_state) { ! char *result; ! char quote; =20=20 /* ! * 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 r= id ! * of OT_SQLIDHACK. */ ! result =3D psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true= ); !=20 ! if (!result) ! return NULL; =20=20 ! if (quote) ! return result; =20=20 ! if (*result =3D=3D '\0' || strcmp(result, "-") =3D=3D 0) ! return NULL; =20=20 ! return result; } =20=20 =20=20 /* ! * Subroutine to actually try to execute a backslash command. */ static backslashResult ! exec_command(const char *cmd, ! PsqlScanState scan_state, ! PQExpBuffer query_buf) { ! bool success =3D true; /* indicate here if the command ran ok or ! * failed */ ! backslashResult status =3D PSQL_CMD_SKIP_LINE; =20=20 ! /* ! * \a -- toggle field alignment This makes little sense but we keep it ! * around. ! */ ! if (strcmp(cmd, "a") =3D=3D 0) { if (pset.popt.topt.format !=3D PRINT_ALIGNED) success =3D do_pset("format", "aligned", &pset.popt, pset.quiet); --- 268,436 ---- return status; } =20=20 +=20 /* ! * 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 =3D conditional_active(cstack); =20=20 /* ! * 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); ! } =20=20 ! if (strcmp(cmd, "a") =3D=3D 0) ! status =3D exec_command_a(scan_state, active_branch); ! else if (strcmp(cmd, "C") =3D=3D 0) ! status =3D exec_command_C(scan_state, active_branch); ! else if (strcmp(cmd, "c") =3D=3D 0 || strcmp(cmd, "connect") =3D=3D 0) ! status =3D exec_command_connect(scan_state, active_branch); ! else if (strcmp(cmd, "cd") =3D=3D 0) ! status =3D exec_command_cd(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "conninfo") =3D=3D 0) ! status =3D exec_command_conninfo(scan_state, active_branch); ! else if (pg_strcasecmp(cmd, "copy") =3D=3D 0) ! status =3D exec_command_copy(scan_state, active_branch); ! else if (strcmp(cmd, "copyright") =3D=3D 0) ! status =3D exec_command_copyright(scan_state, active_branch); ! else if (strcmp(cmd, "crosstabview") =3D=3D 0) ! status =3D exec_command_crosstabview(scan_state, active_branch); ! else if (cmd[0] =3D=3D 'd') ! status =3D exec_command_d(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "e") =3D=3D 0 || strcmp(cmd, "edit") =3D=3D 0) ! status =3D exec_command_edit(scan_state, active_branch, ! query_buf, previous_buf); ! else if (strcmp(cmd, "ef") =3D=3D 0) ! status =3D exec_command_ef(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "ev") =3D=3D 0) ! status =3D exec_command_ev(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "echo") =3D=3D 0 || strcmp(cmd, "qecho") =3D=3D 0) ! status =3D exec_command_echo(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "elif") =3D=3D 0) ! status =3D exec_command_elif(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "else") =3D=3D 0) ! status =3D exec_command_else(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "endif") =3D=3D 0) ! status =3D exec_command_endif(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "encoding") =3D=3D 0) ! status =3D exec_command_encoding(scan_state, active_branch); ! else if (strcmp(cmd, "errverbose") =3D=3D 0) ! status =3D exec_command_errverbose(scan_state, active_branch); ! else if (strcmp(cmd, "f") =3D=3D 0) ! status =3D exec_command_f(scan_state, active_branch); ! else if (strcmp(cmd, "g") =3D=3D 0 || strcmp(cmd, "gx") =3D=3D 0) ! status =3D exec_command_g(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "gexec") =3D=3D 0) ! status =3D exec_command_gexec(scan_state, active_branch); ! else if (strcmp(cmd, "gset") =3D=3D 0) ! status =3D exec_command_gset(scan_state, active_branch); ! else if (strcmp(cmd, "h") =3D=3D 0 || strcmp(cmd, "help") =3D=3D 0) ! status =3D exec_command_help(scan_state, active_branch); ! else if (strcmp(cmd, "H") =3D=3D 0 || strcmp(cmd, "html") =3D=3D 0) ! status =3D exec_command_html(scan_state, active_branch); ! else if (strcmp(cmd, "i") =3D=3D 0 || strcmp(cmd, "include") =3D=3D 0 || ! strcmp(cmd, "ir") =3D=3D 0 || strcmp(cmd, "include_relative") =3D=3D = 0) ! status =3D exec_command_include(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "if") =3D=3D 0) ! status =3D exec_command_if(scan_state, cstack, query_buf); ! else if (strcmp(cmd, "l") =3D=3D 0 || strcmp(cmd, "list") =3D=3D 0 || ! strcmp(cmd, "l+") =3D=3D 0 || strcmp(cmd, "list+") =3D=3D 0) ! status =3D exec_command_list(scan_state, active_branch, cmd); ! else if (strncmp(cmd, "lo_", 3) =3D=3D 0) ! status =3D exec_command_lo(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "o") =3D=3D 0 || strcmp(cmd, "out") =3D=3D 0) ! status =3D exec_command_out(scan_state, active_branch); ! else if (strcmp(cmd, "p") =3D=3D 0 || strcmp(cmd, "print") =3D=3D 0) ! status =3D exec_command_print(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "password") =3D=3D 0) ! status =3D exec_command_password(scan_state, active_branch); ! else if (strcmp(cmd, "prompt") =3D=3D 0) ! status =3D exec_command_prompt(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "pset") =3D=3D 0) ! status =3D exec_command_pset(scan_state, active_branch); ! else if (strcmp(cmd, "q") =3D=3D 0 || strcmp(cmd, "quit") =3D=3D 0) ! status =3D exec_command_quit(scan_state, active_branch); ! else if (strcmp(cmd, "r") =3D=3D 0 || strcmp(cmd, "reset") =3D=3D 0) ! status =3D exec_command_reset(scan_state, active_branch, query_buf); ! else if (strcmp(cmd, "s") =3D=3D 0) ! status =3D exec_command_s(scan_state, active_branch); ! else if (strcmp(cmd, "set") =3D=3D 0) ! status =3D exec_command_set(scan_state, active_branch); ! else if (strcmp(cmd, "setenv") =3D=3D 0) ! status =3D exec_command_setenv(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "sf") =3D=3D 0 || strcmp(cmd, "sf+") =3D=3D 0) ! status =3D exec_command_sf(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "sv") =3D=3D 0 || strcmp(cmd, "sv+") =3D=3D 0) ! status =3D exec_command_sv(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "t") =3D=3D 0) ! status =3D exec_command_t(scan_state, active_branch); ! else if (strcmp(cmd, "T") =3D=3D 0) ! status =3D exec_command_T(scan_state, active_branch); ! else if (strcmp(cmd, "timing") =3D=3D 0) ! status =3D exec_command_timing(scan_state, active_branch); ! else if (strcmp(cmd, "unset") =3D=3D 0) ! status =3D exec_command_unset(scan_state, active_branch, cmd); ! else if (strcmp(cmd, "w") =3D=3D 0 || strcmp(cmd, "write") =3D=3D 0) ! status =3D exec_command_write(scan_state, active_branch, cmd, ! query_buf, previous_buf); ! else if (strcmp(cmd, "watch") =3D=3D 0) ! status =3D exec_command_watch(scan_state, active_branch, ! query_buf, previous_buf); ! else if (strcmp(cmd, "x") =3D=3D 0) ! status =3D exec_command_x(scan_state, active_branch); ! else if (strcmp(cmd, "z") =3D=3D 0) ! status =3D exec_command_z(scan_state, active_branch); ! else if (strcmp(cmd, "!") =3D=3D 0) ! status =3D exec_command_shell_escape(scan_state, active_branch); ! else if (strcmp(cmd, "?") =3D=3D 0) ! status =3D exec_command_slash_command_help(scan_state, active_branch); ! else ! status =3D PSQL_CMD_UNKNOWN; =20=20 ! /* ! * All the commands that return PSQL_CMD_SEND want to execute previous_b= uf ! * if query_buf is empty. For convenience we implement that here, not in ! * the individual command subroutines. ! */ ! if (status =3D=3D PSQL_CMD_SEND) ! copy_previous_query(query_buf, previous_buf); =20=20 ! return status; } =20=20 =20=20 /* ! * \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 =3D true; =20=20 ! if (active_branch) { if (pset.popt.topt.format !=3D PRINT_ALIGNED) success =3D do_pset("format", "aligned", &pset.popt, pset.quiet); *************** exec_command(const char *cmd, *** 216,223 **** success =3D do_pset("format", "unaligned", &pset.popt, pset.quiet); } =20=20 ! /* \C -- override table title (formerly change HTML caption) */ ! else if (strcmp(cmd, "C") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 438,455 ---- success =3D do_pset("format", "unaligned", &pset.popt, pset.quiet); } =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \C -- override table title (formerly change HTML caption) ! */ ! static backslashResult ! exec_command_C(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 225,244 **** success =3D do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } =20=20 ! /* ! * \c or \connect -- connect to database using the specified parameters. ! * ! * \c [-reuse-previous=3DBOOL] 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") =3D=3D 0 || strcmp(cmd, "connect") =3D=3D 0) { static const char prefix[] =3D "-reuse-previous=3D"; char *opt1, --- 457,488 ---- success =3D do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \c or \connect -- connect to database using the specified parameters. ! * ! * \c [-reuse-previous=3DBOOL] 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 ho= st ! * 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 =3D true; !=20 ! if (active_branch) { static const char prefix[] =3D "-reuse-previous=3D"; char *opt1, *************** exec_command(const char *cmd, *** 277,285 **** } free(opt1); } =20=20 ! /* \cd */ ! else if (strcmp(cmd, "cd") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 521,541 ---- } free(opt1); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \cd -- change directory ! */ ! static backslashResult ! exec_command_cd(PsqlScanState scan_state, bool active_branch, const char = *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 323,331 **** if (opt) free(opt); } =20=20 ! /* \conninfo -- display information about the current connection */ ! else if (strcmp(cmd, "conninfo") =3D=3D 0) { char *db =3D PQdb(pset.db); =20=20 --- 579,597 ---- if (opt) free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \conninfo -- display information about the current connection ! */ ! static backslashResult ! exec_command_conninfo(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *db =3D PQdb(pset.db); =20=20 *************** exec_command(const char *cmd, *** 366,373 **** } } =20=20 ! /* \copy */ ! else if (pg_strcasecmp(cmd, "copy") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 632,649 ---- } } =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \copy -- run a COPY command ! */ ! static backslashResult ! exec_command_copy(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); *************** exec_command(const char *cmd, *** 375,387 **** success =3D do_copy(opt); free(opt); } =20=20 ! /* \copyright */ ! else if (strcmp(cmd, "copyright") =3D=3D 0) print_copyright(); =20=20 ! /* \crosstabview -- execute a query and display results in crosstab */ ! else if (strcmp(cmd, "crosstabview") =3D=3D 0) { int i; =20=20 --- 651,683 ---- success =3D do_copy(opt); free(opt); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \copyright ! */ ! static backslashResult ! exec_command_copyright(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) print_copyright(); =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \crosstabview -- execute a query and display results in crosstab ! */ ! static backslashResult ! exec_command_crosstabview(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { int i; =20=20 *************** exec_command(const char *cmd, *** 391,399 **** pset.crosstab_flag =3D true; status =3D PSQL_CMD_SEND; } =20=20 ! /* \d* commands */ ! else if (cmd[0] =3D=3D 'd') { char *pattern; bool show_verbose, --- 687,708 ---- pset.crosstab_flag =3D true; status =3D PSQL_CMD_SEND; } + else + ignore_slash_options(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \d* commands ! */ ! static backslashResult ! exec_command_d(PsqlScanState scan_state, bool active_branch, const char *= cmd) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; ! bool success =3D true; !=20 ! if (active_branch) { char *pattern; bool show_verbose, *************** exec_command(const char *cmd, *** 502,508 **** success =3D listDbRoleSettings(pattern, pattern2); } else ! success =3D PSQL_CMD_UNKNOWN; break; case 'R': switch (cmd[2]) --- 811,817 ---- success =3D listDbRoleSettings(pattern, pattern2); } else ! status =3D PSQL_CMD_UNKNOWN; break; case 'R': switch (cmd[2]) *************** exec_command(const char *cmd, *** 580,592 **** if (pattern) free(pattern); } =20=20 =20=20 ! /* ! * \e or \edit -- edit the current query buffer, or edit a file and make ! * it the query buffer ! */ ! else if (strcmp(cmd, "e") =3D=3D 0 || strcmp(cmd, "edit") =3D=3D 0) { if (!query_buf) { --- 889,914 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); =20=20 + if (!success) + status =3D PSQL_CMD_ERROR; =20=20 ! return status; ! } !=20 ! /* ! * \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 =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { if (!query_buf) { *************** exec_command(const char *cmd, *** 632,637 **** --- 954,963 ---- expand_tilde(&fname); if (fname) canonicalize_path(fname); +=20 + /* Applies to previous query if current buffer is empty */ + copy_previous_query(query_buf, previous_buf); +=20 if (do_edit(fname, query_buf, lineno, NULL)) status =3D PSQL_CMD_NEWEDIT; else *************** exec_command(const char *cmd, *** 643,655 **** free(ln); } } =20=20 ! /* ! * \ef -- edit the named function, or present a blank CREATE FUNCTION ! * template if no argument is given ! */ ! else if (strcmp(cmd, "ef") =3D=3D 0) { int lineno =3D -1; =20=20 if (pset.sversion < 80400) --- 969,994 ---- free(ln); } } + else + ignore_slash_options(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \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 =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { + char *func =3D psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); int lineno =3D -1; =20=20 if (pset.sversion < 80400) *************** exec_command(const char *cmd, *** 668,678 **** } else { - char *func; Oid foid =3D InvalidOid; =20=20 - func =3D psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, true); lineno =3D strip_lineno_from_objdesc(func); if (lineno =3D=3D 0) { --- 1007,1014 ---- *************** exec_command(const char *cmd, *** 725,733 **** lines++; } } -=20 - if (func) - free(func); } =20=20 if (status !=3D PSQL_CMD_ERROR) --- 1061,1066 ---- *************** exec_command(const char *cmd, *** 741,754 **** else status =3D PSQL_CMD_NEWEDIT; } } =20=20 ! /* ! * \ev -- edit the named view, or present a blank CREATE VIEW template if ! * no argument is given ! */ ! else if (strcmp(cmd, "ev") =3D=3D 0) { int lineno =3D -1; =20=20 if (pset.sversion < 70400) --- 1074,1103 ---- else status =3D PSQL_CMD_NEWEDIT; } +=20 + if (func) + free(func); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \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 =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { + char *view =3D psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); int lineno =3D -1; =20=20 if (pset.sversion < 70400) *************** exec_command(const char *cmd, *** 767,777 **** } else { - char *view; Oid view_oid =3D InvalidOid; =20=20 - view =3D psql_scan_slash_option(scan_state, - OT_WHOLE_LINE, NULL, true); lineno =3D strip_lineno_from_objdesc(view); if (lineno =3D=3D 0) { --- 1116,1123 ---- *************** exec_command(const char *cmd, *** 796,804 **** /* error already reported */ status =3D PSQL_CMD_ERROR; } -=20 - if (view) - free(view); } =20=20 if (status !=3D PSQL_CMD_ERROR) --- 1142,1147 ---- *************** exec_command(const char *cmd, *** 812,821 **** else status =3D PSQL_CMD_NEWEDIT; } } =20=20 ! /* \echo and \qecho */ ! else if (strcmp(cmd, "echo") =3D=3D 0 || strcmp(cmd, "qecho") =3D=3D 0) { char *value; char quoted; --- 1155,1177 ---- else status =3D PSQL_CMD_NEWEDIT; } +=20 + if (view) + free(view); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \echo and \qecho -- echo arguments to stdout or query output ! */ ! static backslashResult ! exec_command_echo(PsqlScanState scan_state, bool active_branch, const cha= r *cmd) ! { ! if (active_branch) { char *value; char quoted; *************** exec_command(const char *cmd, *** 846,854 **** if (!no_newline) fputs("\n", fout); } =20=20 ! /* \encoding -- set/show client side encoding */ ! else if (strcmp(cmd, "encoding") =3D=3D 0) { char *encoding =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1202,1220 ---- if (!no_newline) fputs("\n", fout); } + else + ignore_slash_options(scan_state); =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \encoding -- set/show client side encoding ! */ ! static backslashResult ! exec_command_encoding(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *encoding =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 874,882 **** free(encoding); } } =20=20 ! /* \errverbose -- display verbose message from last failed query */ ! else if (strcmp(cmd, "errverbose") =3D=3D 0) { if (pset.last_error_result) { --- 1240,1258 ---- free(encoding); } } + else + ignore_slash_options(scan_state); =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \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.")); } =20=20 ! /* \f -- change field separator */ ! else if (strcmp(cmd, "f") =3D=3D 0) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1273,1290 ---- puts(_("There is no previous error.")); } =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \f -- change field separator ! */ ! static backslashResult ! exec_command_f(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 906,917 **** success =3D do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } =20=20 ! /* ! * \g [filename] -- send query, optionally with output to file/pipe ! * \gx [filename] -- same as \g, with expanded mode forced ! */ ! else if (strcmp(cmd, "g") =3D=3D 0 || strcmp(cmd, "gx") =3D=3D 0) { char *fname =3D psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); --- 1292,1313 ---- success =3D do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \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 =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { char *fname =3D psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); *************** exec_command(const char *cmd, *** 928,943 **** pset.g_expanded =3D true; status =3D PSQL_CMD_SEND; } =20=20 ! /* \gexec -- send query and execute each field of result */ ! else if (strcmp(cmd, "gexec") =3D=3D 0) { pset.gexec_flag =3D true; status =3D PSQL_CMD_SEND; } =20=20 ! /* \gset [prefix] -- send query and store result into variables */ ! else if (strcmp(cmd, "gset") =3D=3D 0) { char *prefix =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1324,1361 ---- pset.g_expanded =3D true; status =3D PSQL_CMD_SEND; } + else + ignore_slash_filepipe(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \gexec -- send query and execute each field of result ! */ ! static backslashResult ! exec_command_gexec(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { pset.gexec_flag =3D true; status =3D PSQL_CMD_SEND; } =20=20 ! return status; ! } !=20 ! /* ! * \gset [prefix] -- send query and store result into variables ! */ ! static backslashResult ! exec_command_gset(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { char *prefix =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 952,960 **** /* gset_prefix is freed later */ status =3D PSQL_CMD_SEND; } =20=20 ! /* help */ ! else if (strcmp(cmd, "h") =3D=3D 0 || strcmp(cmd, "help") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 1370,1388 ---- /* gset_prefix is freed later */ status =3D PSQL_CMD_SEND; } + else + ignore_slash_options(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \help [topic] -- print help about SQL commands ! */ ! static backslashResult ! exec_command_help(PsqlScanState scan_state, bool active_branch) ! { ! if (active_branch) { char *opt =3D 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); } =20=20 ! /* HTML mode */ ! else if (strcmp(cmd, "H") =3D=3D 0 || strcmp(cmd, "html") =3D=3D 0) { if (pset.popt.topt.format !=3D PRINT_HTML) success =3D 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); =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \H and \html -- toggle HTML formatting ! */ ! static backslashResult ! exec_command_html(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { if (pset.popt.topt.format !=3D PRINT_HTML) success =3D do_pset("format", "html", &pset.popt, pset.quiet); *************** exec_command(const char *cmd, *** 983,992 **** success =3D do_pset("format", "aligned", &pset.popt, pset.quiet); } =20=20 =20=20 ! /* \i and \ir include files */ ! else if (strcmp(cmd, "i") =3D=3D 0 || strcmp(cmd, "include") =3D=3D 0 ! || strcmp(cmd, "ir") =3D=3D 0 || strcmp(cmd, "include_relative") =3D= =3D 0) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 1423,1440 ---- success =3D do_pset("format", "aligned", &pset.popt, pset.quiet); } =20=20 + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; + } =20=20 ! /* ! * \i and \ir -- include a file ! */ ! static backslashResult ! exec_command_include(PsqlScanState scan_state, bool active_branch, const = char *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1007,1016 **** free(fname); } } =20=20 ! /* \l is list databases */ ! else if (strcmp(cmd, "l") =3D=3D 0 || strcmp(cmd, "list") =3D=3D 0 || ! strcmp(cmd, "l+") =3D=3D 0 || strcmp(cmd, "list+") =3D=3D 0) { char *pattern; bool show_verbose; --- 1455,1708 ---- free(fname); } } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \if -- beginning of an \if..\endif block ! * ! * is parsed as a boolean expression. Invalid expressions will em= it a ! * warning and be treated as false. Statements that follow a false expre= ssion ! * will be parsed but ignored. Note that in the case where an \if statem= ent ! * 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); !=20 ! /* Remember current query state in case we need to restore later */ ! save_query_text_state(scan_state, cstack, query_buf); !=20 ! /* ! * 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); !=20 ! /* Remember current query state in case we need to restore later */ ! save_query_text_state(scan_state, cstack, query_buf); !=20 ! ignore_boolean_expression(scan_state); ! } !=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \elif -- alternative branch in an \if..\endif block ! * ! * is evaluated the same as in \if . ! */ ! static backslashResult ! exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! bool success =3D true; !=20 ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: !=20 ! /* ! * 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); !=20 ! /* ! * 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: !=20 ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); !=20 ! /* ! * 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: !=20 ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); !=20 ! /* ! * 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 =3D false; ! break; ! case IFSTATE_NONE: ! /* no \if to elif from */ ! psql_error("\\elif: no matching \\if\n"); ! success =3D false; ! break; ! } !=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \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 =3D true; !=20 ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: !=20 ! /* ! * 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); !=20 ! /* Now skip the \else branch */ ! conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); ! break; ! case IFSTATE_FALSE: !=20 ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); !=20 ! /* ! * 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: !=20 ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); !=20 ! /* ! * 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 =3D false; ! break; ! case IFSTATE_NONE: ! /* no \if to else from */ ! psql_error("\\else: no matching \\if\n"); ! success =3D false; ! break; ! } !=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \endif -- ends an \if...\endif block ! */ ! static backslashResult ! exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack, ! PQExpBuffer query_buf) ! { ! bool success =3D true; !=20 ! switch (conditional_stack_peek(cstack)) ! { ! case IFSTATE_TRUE: ! case IFSTATE_ELSE_TRUE: ! /* Close the \if block, keeping the query text */ ! success =3D conditional_stack_pop(cstack); ! Assert(success); ! break; ! case IFSTATE_FALSE: ! case IFSTATE_IGNORED: ! case IFSTATE_ELSE_FALSE: !=20 ! /* ! * Discard any query text added by the just-skipped branch. ! */ ! discard_query_text(scan_state, cstack, query_buf); !=20 ! /* Close the \if block */ ! success =3D conditional_stack_pop(cstack); ! Assert(success); ! break; ! case IFSTATE_NONE: ! /* no \if to end */ ! psql_error("\\endif: no matching \\if\n"); ! success =3D false; ! break; ! } !=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \l -- list databases ! */ ! static backslashResult ! exec_command_list(PsqlScanState scan_state, bool active_branch, const cha= r *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *pattern; bool show_verbose; *************** exec_command(const char *cmd, *** 1025,1035 **** if (pattern) free(pattern); } =20=20 ! /* ! * large object things ! */ ! else if (strncmp(cmd, "lo_", 3) =3D=3D 0) { char *opt1, *opt2; --- 1717,1738 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \lo_* -- large object operations ! */ ! static backslashResult ! exec_command_lo(PsqlScanState scan_state, bool active_branch, const char = *cmd) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; ! bool success =3D true; !=20 ! if (active_branch) { char *opt1, *opt2; *************** exec_command(const char *cmd, *** 1087,1096 **** free(opt1); free(opt2); } =20=20 =20=20 ! /* \o -- set query output */ ! else if (strcmp(cmd, "o") =3D=3D 0 || strcmp(cmd, "out") =3D=3D 0) { char *fname =3D psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); --- 1790,1813 ---- free(opt1); free(opt2); } + else + ignore_slash_options(scan_state); =20=20 + if (!success) + status =3D PSQL_CMD_ERROR; =20=20 ! return status; ! } !=20 ! /* ! * \o -- set query output ! */ ! static backslashResult ! exec_command_out(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *fname =3D psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); *************** exec_command(const char *cmd, *** 1099,1107 **** success =3D setQFout(fname); free(fname); } =20=20 ! /* \p prints the current query buffer */ ! else if (strcmp(cmd, "p") =3D=3D 0 || strcmp(cmd, "print") =3D=3D 0) { if (query_buf && query_buf->len > 0) puts(query_buf->data); --- 1816,1835 ---- success =3D setQFout(fname); free(fname); } + else + ignore_slash_filepipe(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \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); } =20=20 ! /* \password -- set user password */ ! else if (strcmp(cmd, "password") =3D=3D 0) { char pw1[100]; char pw2[100]; =20=20 --- 1838,1858 ---- fflush(stdout); } =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \password -- set user password ! */ ! static backslashResult ! exec_command_password(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { + char *opt0 =3D psql_scan_slash_option(scan_state, + OT_SQLID, NULL, true); char pw1[100]; char pw2[100]; =20=20 *************** exec_command(const char *cmd, *** 1126,1132 **** } else { - char *opt0 =3D psql_scan_slash_option(scan_state, OT_SQLID, NULL, t= rue); char *user; char *encrypted_password; =20=20 --- 1866,1871 ---- *************** exec_command(const char *cmd, *** 1159,1172 **** PQclear(res); PQfreemem(encrypted_password); } -=20 - if (opt0) - free(opt0); } } =20=20 ! /* \prompt -- prompt and set variable */ ! else if (strcmp(cmd, "prompt") =3D=3D 0) { char *opt, *prompt_text =3D NULL; --- 1898,1924 ---- PQclear(res); PQfreemem(encrypted_password); } } +=20 + if (opt0) + free(opt0); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \prompt -- prompt and set variable ! */ ! static backslashResult ! exec_command_prompt(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt, *prompt_text =3D NULL; *************** exec_command(const char *cmd, *** 1225,1233 **** free(opt); } } =20=20 ! /* \pset -- set printing parameters */ ! else if (strcmp(cmd, "pset") =3D=3D 0) { char *opt0 =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 1977,1997 ---- free(opt); } } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \pset -- set printing parameters ! */ ! static backslashResult ! exec_command_pset(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt0 =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1267,1279 **** free(opt0); free(opt1); } =20=20 ! /* \q or \quit */ ! else if (strcmp(cmd, "q") =3D=3D 0 || strcmp(cmd, "quit") =3D=3D 0) status =3D PSQL_CMD_TERMINATE; =20=20 ! /* reset(clear) the buffer */ ! else if (strcmp(cmd, "r") =3D=3D 0 || strcmp(cmd, "reset") =3D=3D 0) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); --- 2031,2064 ---- free(opt0); free(opt1); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \q or \quit -- exit psql ! */ ! static backslashResult ! exec_command_quit(PsqlScanState scan_state, bool active_branch) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) status =3D PSQL_CMD_TERMINATE; =20=20 ! return status; ! } !=20 ! /* ! * \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).")); } =20=20 ! /* \s save history in a file or show it on the screen */ ! else if (strcmp(cmd, "s") =3D=3D 0) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2066,2083 ---- puts(_("Query buffer reset (cleared).")); } =20=20 ! return PSQL_CMD_SKIP_LINE; ! } !=20 ! /* ! * \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 =3D true; !=20 ! if (active_branch) { char *fname =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1295,1303 **** putchar('\n'); free(fname); } =20=20 ! /* \set -- generalized set variable/option command */ ! else if (strcmp(cmd, "set") =3D=3D 0) { char *opt0 =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2090,2110 ---- putchar('\n'); free(fname); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \set -- set variable ! */ ! static backslashResult ! exec_command_set(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt0 =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1336,1345 **** } free(opt0); } =20=20 =20=20 ! /* \setenv -- set environment command */ ! else if (strcmp(cmd, "setenv") =3D=3D 0) { char *envvar =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2143,2164 ---- } free(opt0); } + else + ignore_slash_options(scan_state); =20=20 + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; + } =20=20 ! /* ! * \setenv -- set environment variable ! */ ! static backslashResult ! exec_command_setenv(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *envvar =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1381,1389 **** free(envvar); free(envval); } =20=20 ! /* \sf -- show a function's source code */ ! else if (strcmp(cmd, "sf") =3D=3D 0 || strcmp(cmd, "sf+") =3D=3D 0) { bool show_linenumbers =3D (strcmp(cmd, "sf+") =3D=3D 0); PQExpBuffer func_buf; --- 2200,2220 ---- free(envvar); free(envval); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \sf -- show a function's source code ! */ ! static backslashResult ! exec_command_sf(PsqlScanState scan_state, bool active_branch, const char = *cmd) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { bool show_linenumbers =3D (strcmp(cmd, "sf+") =3D=3D 0); PQExpBuffer func_buf; *************** exec_command(const char *cmd, *** 1463,1471 **** free(func); destroyPQExpBuffer(func_buf); } =20=20 ! /* \sv -- show a view's source code */ ! else if (strcmp(cmd, "sv") =3D=3D 0 || strcmp(cmd, "sv+") =3D=3D 0) { bool show_linenumbers =3D (strcmp(cmd, "sv+") =3D=3D 0); PQExpBuffer view_buf; --- 2294,2314 ---- free(func); destroyPQExpBuffer(func_buf); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \sv -- show a view's source code ! */ ! static backslashResult ! exec_command_sv(PsqlScanState scan_state, bool active_branch, const char = *cmd) ! { ! backslashResult status =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { bool show_linenumbers =3D (strcmp(cmd, "sv+") =3D=3D 0); PQExpBuffer view_buf; *************** exec_command(const char *cmd, *** 1539,1547 **** free(view); destroyPQExpBuffer(view_buf); } =20=20 ! /* \t -- turn off headers and row count */ ! else if (strcmp(cmd, "t") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2382,2402 ---- free(view); destroyPQExpBuffer(view_buf); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \t -- turn off table headers and row count ! */ ! static backslashResult ! exec_command_t(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1549,1557 **** success =3D do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } =20=20 ! /* \T -- define html attributes */ ! else if (strcmp(cmd, "T") =3D=3D 0) { char *value =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2404,2424 ---- success =3D do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \T -- define html
attributes ! */ ! static backslashResult ! exec_command_T(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *value =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1559,1567 **** success =3D do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } =20=20 ! /* \timing -- toggle timing of queries */ ! else if (strcmp(cmd, "timing") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2426,2446 ---- success =3D do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \timing -- enable/disable timing of queries ! */ ! static backslashResult ! exec_command_timing(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1579,1587 **** } free(opt); } =20=20 ! /* \unset */ ! else if (strcmp(cmd, "unset") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2458,2479 ---- } free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \unset -- unset variable ! */ ! static backslashResult ! exec_command_unset(PsqlScanState scan_state, bool active_branch, ! const char *cmd) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); *************** exec_command(const char *cmd, *** 1596,1608 **** =20=20 free(opt); } =20=20 ! /* \w -- write query buffer to file */ ! else if (strcmp(cmd, "w") =3D=3D 0 || strcmp(cmd, "write") =3D=3D 0) { FILE *fd =3D NULL; bool is_pipe =3D false; - char *fname =3D NULL; =20=20 if (!query_buf) { --- 2488,2515 ---- =20=20 free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \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 =3D PSQL_CMD_SKIP_LINE; !=20 ! if (active_branch) { + char *fname =3D psql_scan_slash_option(scan_state, + OT_FILEPIPE, NULL, true); FILE *fd =3D NULL; bool is_pipe =3D false; =20=20 if (!query_buf) { *************** exec_command(const char *cmd, *** 1611,1627 **** } else { - fname =3D psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, true); - expand_tilde(&fname); -=20 if (!fname) { psql_error("\\%s: missing required argument\n", cmd); ! success =3D false; } else { if (fname[0] =3D=3D '|') { is_pipe =3D true; --- 2518,2531 ---- } else { if (!fname) { psql_error("\\%s: missing required argument\n", cmd); ! status =3D PSQL_CMD_ERROR; } else { + expand_tilde(&fname); if (fname[0] =3D=3D '|') { is_pipe =3D true; *************** exec_command(const char *cmd, *** 1636,1642 **** if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); ! success =3D false; } } } --- 2540,2546 ---- if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); ! status =3D PSQL_CMD_ERROR; } } } *************** exec_command(const char *cmd, *** 1647,1652 **** --- 2551,2559 ---- =20=20 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); =20=20 if (is_pipe) result =3D pclose(fd); *************** exec_command(const char *cmd, *** 1656,1662 **** if (result =3D=3D EOF) { psql_error("%s: %s\n", fname, strerror(errno)); ! success =3D false; } } =20=20 --- 2563,2569 ---- if (result =3D=3D EOF) { psql_error("%s: %s\n", fname, strerror(errno)); ! status =3D PSQL_CMD_ERROR; } } =20=20 *************** exec_command(const char *cmd, *** 1665,1673 **** =20=20 free(fname); } =20=20 ! /* \watch -- execute a query every N seconds */ ! else if (strcmp(cmd, "watch") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2572,2593 ---- =20=20 free(fname); } + else + ignore_slash_filepipe(scan_state); =20=20 ! return status; ! } !=20 ! /* ! * \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 =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1682,1696 **** free(opt); } =20=20 success =3D do_watch(query_buf, sleep); =20=20 /* Reset the query buffer as though for \r */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } =20=20 ! /* \x -- set or toggle expanded table representation */ ! else if (strcmp(cmd, "x") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2602,2631 ---- free(opt); } =20=20 + /* Applies to previous query if current buffer is empty */ + copy_previous_query(query_buf, previous_buf); +=20 success =3D do_watch(query_buf, sleep); =20=20 /* Reset the query buffer as though for \r */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \x -- set or toggle expanded table representation ! */ ! static backslashResult ! exec_command_x(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1698,1706 **** success =3D do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } =20=20 ! /* \z -- list table rights (equivalent to \dp) */ ! else if (strcmp(cmd, "z") =3D=3D 0) { char *pattern =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); --- 2633,2653 ---- success =3D do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \z -- list table privileges (equivalent to \dp) ! */ ! static backslashResult ! exec_command_z(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *pattern =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); *************** exec_command(const char *cmd, *** 1709,1717 **** if (pattern) free(pattern); } =20=20 ! /* \! -- shell escape */ ! else if (strcmp(cmd, "!") =3D=3D 0) { char *opt =3D psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); --- 2656,2676 ---- if (pattern) free(pattern); } + else + ignore_slash_options(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \! -- execute shell command ! */ ! static backslashResult ! exec_command_shell_escape(PsqlScanState scan_state, bool active_branch) ! { ! bool success =3D true; !=20 ! if (active_branch) { char *opt =3D psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); *************** exec_command(const char *cmd, *** 1719,1727 **** success =3D do_shell(opt); free(opt); } =20=20 ! /* \? -- slash command help */ ! else if (strcmp(cmd, "?") =3D=3D 0) { char *opt0 =3D psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); --- 2678,2696 ---- success =3D do_shell(opt); free(opt); } + else + ignore_slash_whole_line(scan_state); =20=20 ! return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; ! } !=20 ! /* ! * \? -- print help about backslash commands ! */ ! static backslashResult ! exec_command_slash_command_help(PsqlScanState scan_state, bool active_bra= nch) ! { ! if (active_branch) { char *opt0 =3D 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); } =20=20 ! #if 0 =20=20 /* ! * These commands don't do anything. I just use them to test the parser. */ ! else if (strcmp(cmd, "void") =3D=3D 0 || strcmp(cmd, "#") =3D=3D 0) ! { ! int i =3D 0; ! char *value; =20=20 ! while ((value =3D psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, true))) ! { ! psql_error("+ opt(%d) =3D |%s|\n", i++, value); ! free(value); ! } } - #endif =20=20 ! else ! status =3D PSQL_CMD_UNKNOWN; =20=20 ! if (!success) ! status =3D PSQL_CMD_ERROR; =20=20 ! return status; } =20=20 /* --- 2703,2933 ---- helpVariables(pset.popt.topt.pager); else slashUsage(pset.popt.topt.pager); +=20 + if (opt0) + free(opt0); } + else + ignore_slash_options(scan_state); +=20 + return PSQL_CMD_SKIP_LINE; + } =20=20 !=20 ! /* ! * Read and interpret an argument to the \connect slash command. ! */ ! static char * ! read_connect_arg(PsqlScanState scan_state) ! { ! char *result; ! char quote; =20=20 /* ! * 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 r= id ! * of OT_SQLIDHACK. */ ! result =3D psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true= ); =20=20 ! if (!result) ! return NULL; !=20 ! if (quote) ! return result; !=20 ! if (*result =3D=3D '\0' || strcmp(result, "-") =3D=3D 0) ! return NULL; !=20 ! return result; ! } !=20 ! /* ! * 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 =3D createPQExpBuffer(); ! int num_options =3D 0; ! char *value; !=20 ! /* collect all arguments for the conditional command into exp_buf */ ! while ((value =3D psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false)) !=3D NULL) ! { ! /* add spaces between tokens */ ! if (num_options > 0) ! appendPQExpBufferChar(exp_buf, ' '); ! appendPQExpBufferStr(exp_buf, value); ! num_options++; ! free(value); } =20=20 ! return exp_buf; ! } =20=20 ! /* ! * 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 =3D gather_boolean_expression(scan_state); ! bool value =3D false; ! bool success =3D ParseVariableBool(buf->data, name, &value); =20=20 ! destroyPQExpBuffer(buf); ! return success && value; ! } !=20 ! /* ! * 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 =3D gather_boolean_expression(scan_state); !=20 ! destroyPQExpBuffer(buf); ! } !=20 ! /* ! * 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 any= way. ! */ ! static void ! ignore_slash_options(PsqlScanState scan_state) ! { ! char *arg; !=20 ! while ((arg =3D psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false)) !=3D NULL) ! free(arg); ! } !=20 ! /* ! * 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 differ= ent ! * amount of option text in active and inactive cases. ! */ ! static void ! ignore_slash_filepipe(PsqlScanState scan_state) ! { ! char *arg =3D psql_scan_slash_option(scan_state, ! OT_FILEPIPE, NULL, false); !=20 ! if (arg) ! free(arg); ! } !=20 ! /* ! * 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 diff= erent ! * amount of option text in active and inactive cases. ! */ ! static void ! ignore_slash_whole_line(PsqlScanState scan_state) ! { ! char *arg =3D psql_scan_slash_option(scan_state, ! OT_WHOLE_LINE, NULL, false); !=20 ! if (arg) ! free(arg); ! } !=20 ! /* ! * Return true if the command given is a branching command. ! */ ! static bool ! is_branching_command(const char *cmd) ! { ! return (strcmp(cmd, "if") =3D=3D 0 || ! strcmp(cmd, "elif") =3D=3D 0 || ! strcmp(cmd, "else") =3D=3D 0 || ! strcmp(cmd, "endif") =3D=3D 0); ! } !=20 ! /* ! * 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)); ! } !=20 ! /* ! * 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_bu= f. ! * ! * 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 =3D conditional_stack_get_query_len(cstack); !=20 ! Assert(new_len >=3D 0 && new_len <=3D query_buf->len); ! query_buf->len =3D new_len; ! query_buf->data[new_len] =3D '\0'; ! } ! psql_scan_set_paren_depth(scan_state, ! conditional_stack_get_paren_depth(cstack)); ! } !=20 ! /* ! * 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 =3D=3D NULL (and do nothing). ! */ ! static void ! copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf) ! { ! if (query_buf && query_buf->len =3D=3D 0) ! appendPQExpBufferStr(query_buf, previous_buf->data); } =20=20 /* 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 ---- =20=20 #include "fe_utils/print.h" #include "fe_utils/psqlscan.h" + #include "conditional.h" =20=20 =20=20 typedef enum _backslashResult *************** typedef enum _backslashResult *** 25,31 **** =20=20 =20=20 extern backslashResult HandleSlashCmds(PsqlScanState scan_state, ! PQExpBuffer query_buf); =20=20 extern int process_file(char *filename, bool use_relative_path); =20=20 --- 26,34 ---- =20=20 =20=20 extern backslashResult HandleSlashCmds(PsqlScanState scan_state, ! ConditionalStack cstack, ! PQExpBuffer query_buf, ! PQExpBuffer previous_buf); =20=20 extern int process_file(char *filename, bool use_relative_path); =20=20 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_passthr= ough. ! * 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_passthr= ough. ! * 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; =20=20 + /* In an inactive \if branch, suppress all variable substitutions */ + if (passthrough && !conditional_active((ConditionalStack) passthrough)) + return NULL; +=20 value =3D 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" +=20 + #include "conditional.h" +=20 + /* + * create stack + */ + ConditionalStack + conditional_stack_create(void) + { + ConditionalStack cstack =3D pg_malloc(sizeof(ConditionalStackData)); +=20 + cstack->head =3D NULL; + return cstack; + } +=20 + /* + * destroy stack + */ + void + conditional_stack_destroy(ConditionalStack cstack) + { + while (conditional_stack_pop(cstack)) + continue; + free(cstack); + } +=20 + /* + * Create a new conditional branch. + */ + void + conditional_stack_push(ConditionalStack cstack, ifState new_state) + { + IfStackElem *p =3D (IfStackElem *) pg_malloc(sizeof(IfStackElem)); +=20 + p->if_state =3D new_state; + p->query_len =3D -1; + p->paren_depth =3D -1; + p->next =3D cstack->head; + cstack->head =3D p; + } +=20 + /* + * Destroy the topmost conditional branch. + * Returns false if there was no branch to end. + */ + bool + conditional_stack_pop(ConditionalStack cstack) + { + IfStackElem *p =3D cstack->head; +=20 + if (!p) + return false; + cstack->head =3D cstack->head->next; + free(p); + return true; + } +=20 + /* + * 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; + } +=20 + /* + * 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 =3D new_state; + return true; + } +=20 + /* + * True if there are no active \if-blocks. + */ + bool + conditional_stack_empty(ConditionalStack cstack) + { + return cstack->head =3D=3D NULL; + } +=20 + /* + * 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 =3D conditional_stack_peek(cstack); +=20 + return s =3D=3D IFSTATE_NONE || s =3D=3D IFSTATE_TRUE || s =3D=3D IFSTAT= E_ELSE_TRUE; + } +=20 + /* + * 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 =3D len; + } +=20 + /* + * 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; + } +=20 + /* + * 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 =3D depth; + } +=20 + /* + * 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 +=20 + /* + * Possible states of a single level of \if block. + */ + typedef enum ifState + { + IFSTATE_NONE =3D 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; +=20 + /* + * 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 b= ranch; + * 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 de= pth, + * 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; +=20 + typedef struct ConditionalStackData + { + IfStackElem *head; + } ConditionalStackData; +=20 + typedef struct ConditionalStackData *ConditionalStack; +=20 +=20 + extern ConditionalStack conditional_stack_create(void); +=20 + extern void conditional_stack_destroy(ConditionalStack cstack); +=20 + extern void conditional_stack_push(ConditionalStack cstack, ifState new_s= tate); +=20 + extern bool conditional_stack_pop(ConditionalStack cstack); +=20 + extern ifState conditional_stack_peek(ConditionalStack cstack); +=20 + extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_s= tate); +=20 + extern bool conditional_stack_empty(ConditionalStack cstack); +=20 + extern bool conditional_active(ConditionalStack cstack); +=20 + extern void conditional_stack_set_query_len(ConditionalStack cstack, int = len); +=20 + extern int conditional_stack_get_query_len(ConditionalStack cstack); +=20 + extern void conditional_stack_set_paren_depth(ConditionalStack cstack, in= t depth); +=20 + extern int conditional_stack_get_paren_depth(ConditionalStack cstack); +=20 + #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 =3D get_prompt(PROMPT_COPY); =20=20 fputs(prompt, stdout); fflush(stdout); --- 552,558 ---- /* interactive input probably silly, but give one prompt anyway */ if (showprompt) { ! const char *prompt =3D get_prompt(PROMPT_COPY, NULL); =20=20 fputs(prompt, stdout); fflush(stdout); *************** handleCopyIn(PGconn *conn, FILE *copystr *** 590,596 **** =20=20 if (showprompt) { ! const char *prompt =3D get_prompt(PROMPT_COPY); =20=20 fputs(prompt, stdout); fflush(stdout); --- 590,596 ---- =20=20 if (showprompt) { ! const char *prompt =3D get_prompt(PROMPT_COPY, NULL); =20=20 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=3Dcommands | wc" to count correctly. It's okay to c= ount * the USE_READLINE line even in builds without that. */ ! output =3D PageOutput(113, pager ? &(pset.popt.topt) : NULL); =20=20 fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and d= istribution terms\n")); --- 167,173 ---- * Use "psql --help=3Dcommands | wc" to count correctly. It's okay to c= ount * the USE_READLINE line even in builds without that. */ ! output =3D PageOutput(122, pager ? &(pset.popt.topt) : NULL); =20=20 fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and d= istribution terms\n")); *************** slashUsage(unsigned short int pager) *** 210,215 **** --- 210,222 ---- fprintf(output, _(" \\qecho [STRING] write string to query outpu= t stream (see \\o)\n")); fprintf(output, "\n"); =20=20 + 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 cu= rrent conditional block\n")); + fprintf(output, _(" \\endif end conditional block\n")); + fprintf(output, "\n"); +=20 fprintf(output, _("Informational\n")); fprintf(output, _(" (options: S =3D show system objects, + =3D addition= al detail)\n")); fprintf(output, _(" \\d[S+] list tables, views, and seq= uences\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 =3D PROMPT_READY; volatile int count_eof =3D 0; volatile bool die_on_error =3D false; -=20 - /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; =20=20 ! /* Save old settings */ prev_cmd_source =3D pset.cur_cmd_source; prev_cmd_interactive =3D pset.cur_cmd_interactive; prev_lineno =3D pset.lineno; =20=20 /* Establish new source */ pset.cur_cmd_source =3D source; --- 51,65 ---- volatile promptStatus_t prompt_status =3D PROMPT_READY; volatile int count_eof =3D 0; volatile bool die_on_error =3D false; FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; =20=20 ! /* Save the prior command source */ prev_cmd_source =3D pset.cur_cmd_source; prev_cmd_interactive =3D pset.cur_cmd_interactive; prev_lineno =3D pset.lineno; + /* pset.stmt_lineno does not need to be saved and restored */ =20=20 /* Establish new source */ pset.cur_cmd_source =3D source; *************** MainLoop(FILE *source) *** 69,74 **** --- 69,76 ---- =20=20 /* Create working state */ scan_state =3D psql_scan_create(&psqlscan_callbacks); + cond_stack =3D conditional_stack_create(); + psql_scan_set_passthrough(scan_state, (void *) cond_stack); =20=20 query_buf =3D createPQExpBuffer(); previous_buf =3D createPQExpBuffer(); *************** MainLoop(FILE *source) *** 122,128 **** --- 124,142 ---- cancel_pressed =3D false; =20=20 if (pset.cur_cmd_interactive) + { putc('\n', stdout); +=20 + /* + * 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 =3D EXIT_USER; *************** MainLoop(FILE *source) *** 140,146 **** /* May need to reset prompt, eg after \r command */ if (query_buf->len =3D=3D 0) prompt_status =3D PROMPT_READY; ! line =3D gets_interactive(get_prompt(prompt_status), query_buf); } else { --- 154,161 ---- /* May need to reset prompt, eg after \r command */ if (query_buf->len =3D=3D 0) prompt_status =3D PROMPT_READY; ! line =3D gets_interactive(get_prompt(prompt_status, cond_stack), ! query_buf); } else { *************** MainLoop(FILE *source) *** 286,293 **** (scan_result =3D=3D 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 =3D=3D 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 =3D true; } =20=20 ! /* execute query */ ! success =3D SendQuery(query_buf->data); ! slashCmdStatus =3D success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; ! pset.stmt_lineno =3D 1; !=20 ! /* transfer query to previous_buf by pointer-swapping */ { ! PQExpBuffer swap_buf =3D previous_buf; =20=20 ! previous_buf =3D query_buf; ! query_buf =3D swap_buf; ! } ! resetPQExpBuffer(query_buf); =20=20 ! added_nl_pos =3D -1; ! /* we need not do psql_scan_reset() here */ } else if (scan_result =3D=3D PSCAN_BACKSLASH) { --- 313,348 ---- line_saved_in_history =3D true; } =20=20 ! /* execute query unless we're in an inactive \if branch */ ! if (conditional_active(cond_stack)) { ! success =3D SendQuery(query_buf->data); ! slashCmdStatus =3D success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; ! pset.stmt_lineno =3D 1; =20=20 ! /* transfer query to previous_buf by pointer-swapping */ ! { ! PQExpBuffer swap_buf =3D previous_buf; =20=20 ! previous_buf =3D query_buf; ! query_buf =3D swap_buf; ! } ! resetPQExpBuffer(query_buf); !=20 ! added_nl_pos =3D -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 =3D true; ! slashCmdStatus =3D PSQL_CMD_SEND; ! pset.stmt_lineno =3D 1; ! /* note that query_buf doesn't change state */ ! } } else if (scan_result =3D=3D PSCAN_BACKSLASH) { *************** MainLoop(FILE *source) *** 343,363 **** =20=20 /* execute backslash command */ slashCmdStatus =3D HandleSlashCmds(scan_state, ! query_buf->len > 0 ? ! query_buf : previous_buf); =20=20 success =3D slashCmdStatus !=3D PSQL_CMD_ERROR; - pset.stmt_lineno =3D 1; =20=20 ! if ((slashCmdStatus =3D=3D PSQL_CMD_SEND || slashCmdStatus =3D=3D PSQ= L_CMD_NEWEDIT) && ! query_buf->len =3D=3D 0) ! { ! /* copy previous buffer to current for handling */ ! appendPQExpBufferStr(query_buf, previous_buf->data); ! } =20=20 if (slashCmdStatus =3D=3D PSQL_CMD_SEND) { success =3D SendQuery(query_buf->data); =20=20 /* transfer query to previous_buf by pointer-swapping */ --- 374,397 ---- =20=20 /* execute backslash command */ slashCmdStatus =3D HandleSlashCmds(scan_state, ! cond_stack, ! query_buf, ! previous_buf); =20=20 success =3D slashCmdStatus !=3D PSQL_CMD_ERROR; =20=20 ! /* ! * 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 =3D 1; =20=20 if (slashCmdStatus =3D=3D PSQL_CMD_SEND) { + /* should not see this in inactive branch */ + Assert(conditional_active(cond_stack)); +=20 success =3D SendQuery(query_buf->data); =20=20 /* transfer query to previous_buf by pointer-swapping */ *************** MainLoop(FILE *source) *** 374,379 **** --- 408,415 ---- } else if (slashCmdStatus =3D=3D 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); =20=20 ! /* execute query */ ! success =3D SendQuery(query_buf->data); =20=20 if (!success && die_on_error) successResult =3D EXIT_USER; --- 465,481 ---- if (pset.cur_cmd_interactive) pg_send_history(history_buf); =20=20 ! /* execute query unless we're in an inactive \if branch */ ! if (conditional_active(cond_stack)) ! { ! success =3D 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 =3D true; ! } =20=20 if (!success && die_on_error) successResult =3D EXIT_USER; *************** MainLoop(FILE *source) *** 439,444 **** --- 484,502 ---- } =20=20 /* + * Check for unbalanced \if-\endifs unless user explicitly quit, or the + * script is erroring out + */ + if (slashCmdStatus !=3D PSQL_CMD_TERMINATE && + successResult !=3D 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 =3D EXIT_USER; + } +=20 + /* * 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); =20=20 psql_scan_destroy(scan_state); + conditional_stack_destroy(cond_stack); =20=20 pset.cur_cmd_source =3D prev_cmd_source; pset.cur_cmd_interactive =3D 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 **** */ =20=20 char * ! get_prompt(promptStatus_t status) { #define MAX_PROMPT_SIZE 256 static char destination[MAX_PROMPT_SIZE + 1]; --- 66,72 ---- */ =20=20 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] =3D '!'; else if (!pset.singleline) buf[0] =3D '=3D'; --- 188,196 ---- switch (status) { case PROMPT_READY: ! if (cstack !=3D NULL && !conditional_active(cstack)) ! buf[0] =3D '@'; ! else if (!pset.db) buf[0] =3D '!'; else if (!pset.singleline) buf[0] =3D '=3D'; 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 **** =20=20 /* enum promptStatus_t is now defined by psqlscan.h */ #include "fe_utils/psqlscan.h" =20=20 ! char *get_prompt(promptStatus_t status); =20=20 #endif /* PROMPT_H */ --- 10,17 ---- =20=20 /* enum promptStatus_t is now defined by psqlscan.h */ #include "fe_utils/psqlscan.h" + #include "conditional.h" =20=20 ! char *get_prompt(promptStatus_t status, ConditionalStack cstack); =20=20 #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 */ }; =20=20 =20=20 --- 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 */ }; =20=20 =20=20 *************** extern char *psql_scan_slash_option(Psql *** 32,37 **** --- 31,40 ---- =20=20 extern void psql_scan_slash_command_end(PsqlScanState state); =20=20 + extern int psql_scan_get_paren_depth(PsqlScanState state); +=20 + extern void psql_scan_set_paren_depth(PsqlScanState state, int depth); +=20 extern void dequote_downcase_identifier(char *str, bool downcase, int enc= oding); =20=20 #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" =20=20 #include "psqlscanslash.h" + #include "conditional.h" =20=20 #include "libpq-fe.h" } *************** other . *** 230,237 **** =20=20 :{variable_char}+ { /* Possible psql variable substitution */ ! if (option_type =3D=3D OT_NO_EVAL || ! cur_state->callbacks->get_variable =3D=3D NULL) ECHO; else { --- 231,237 ---- =20=20 :{variable_char}+ { /* Possible psql variable substitution */ ! if (cur_state->callbacks->get_variable =3D=3D NULL) ECHO; else { *************** other . *** 268,292 **** } =20=20 :'{variable_char}+' { ! if (option_type =3D=3D OT_NO_EVAL) ! ECHO; ! else ! { ! psqlscan_escape_variable(cur_state, yytext, yyleng, false); ! *option_quote =3D ':'; ! } unquoted_option_chars =3D 0; } =20=20 =20=20 :\"{variable_char}+\" { ! if (option_type =3D=3D OT_NO_EVAL) ! ECHO; ! else ! { ! psqlscan_escape_variable(cur_state, yytext, yyleng, true); ! *option_quote =3D ':'; ! } unquoted_option_chars =3D 0; } =20=20 --- 268,282 ---- } =20=20 :'{variable_char}+' { ! psqlscan_escape_variable(cur_state, yytext, yyleng, false); ! *option_quote =3D ':'; unquoted_option_chars =3D 0; } =20=20 =20=20 :\"{variable_char}+\" { ! psqlscan_escape_variable(cur_state, yytext, yyleng, true); ! *option_quote =3D ':'; unquoted_option_chars =3D 0; } =20=20 *************** other . *** 353,360 **** */ =20=20 "`" { ! /* In NO_EVAL mode, don't evaluate the command */ ! if (option_type !=3D OT_NO_EVAL) evaluate_backtick(cur_state); BEGIN(xslasharg); } --- 343,351 ---- */ =20=20 "`" { ! /* In an inactive \if branch, don't evaluate the command */ ! if (cur_state->cb_passthrough =3D=3D NULL || ! conditional_active((ConditionalStack) cur_state->cb_passthrough)) evaluate_backtick(cur_state); BEGIN(xslasharg); } *************** psql_scan_slash_command_end(PsqlScanStat *** 642,647 **** --- 633,657 ---- } =20=20 /* + * Fetch current paren nesting depth + */ + int + psql_scan_get_paren_depth(PsqlScanState state) + { + return state->paren_depth; + } +=20 + /* + * Set paren nesting depth + */ + void + psql_scan_set_paren_depth(PsqlScanState state, int depth) + { + Assert(depth >=3D 0); + state->paren_depth =3D depth; + } +=20 + /* * 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 =3D=3D ACT_SINGLE_SLASH) { PsqlScanState scan_state; + ConditionalStack cond_stack; =20=20 if (pset.echo =3D=3D 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()); =20=20 ! successResult =3D HandleSlashCmds(scan_state, NULL) !=3D PSQL_CMD_ERR= OR ? EXIT_SUCCESS : EXIT_FAILURE; =20=20 psql_scan_destroy(scan_state); } else if (cell->action =3D=3D ACT_FILE) { --- 340,356 ---- psql_scan_setup(scan_state, cell->val, strlen(cell->val), pset.encoding, standard_strings()); + cond_stack =3D conditional_stack_create(); + psql_scan_set_passthrough(scan_state, (void *) cond_stack); =20=20 ! successResult =3D HandleSlashCmds(scan_state, ! cond_stack, ! NULL, ! NULL) !=3D PSQL_CMD_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; =20=20 psql_scan_destroy(scan_state); + conditional_stack_destroy(cond_stack); } else if (cell->action =3D=3D 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?=20 + ---------- + okay + (1 row) +=20 + select 'still okay'; + ?column?=20=20 + ------------ + still okay + (1 row) +=20 + \else + not okay; + still not okay + \endif + -- at this point query buffer should still have last valid line + \g + ?column?=20=20 + ------------ + still okay + (1 row) +=20 + -- \if should work okay on part of a query + select + \if true + 42 + \else + (bogus + \endif + forty_two; + forty_two=20 + ----------- + 42 + (1 row) +=20 + select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; + forty_two=20 + ----------- + 42 + (1 row) +=20 + -- 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": boo= lean 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 =20=20 + -- tests for \if ... \endif +=20 + \if true + select 'okay'; + select 'still okay'; + \else + not okay; + still not okay + \endif +=20 + -- at this point query buffer should still have last valid line + \g +=20 + -- \if should work okay on part of a query + select + \if true + 42 + \else + (bogus + \endif + forty_two; +=20 + select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; +=20 + -- 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 +=20 + -- 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 +=20 + -- test simple true-then-else + \if true + \echo 'first thing true' + \else + \echo 'should not print #3-1' + \endif +=20 + -- 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 +=20 + -- invalid boolean expressions are false + \if invalid_boolean_expression + \echo 'will not print #6-1' + \else + \echo 'will print anyway #6-2' + \endif +=20 + -- test un-matched endif + \endif +=20 + -- test un-matched else + \else +=20 + -- test un-matched elif + \elif +=20 + -- test double-else error + \if true + \else + \else + \endif +=20 + -- test elif out-of-order + \if false + \else + \elif + \endif +=20 + -- 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 +=20 + -- 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" +=20 + -- 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 +=20 -- SHOW_CONTEXT =20=20 \set SHOW_CONTEXT never ------- =_aaaaaaaaaa0 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers ------- =_aaaaaaaaaa0--