mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
Support \if ... \elif ... \else ... \endif in psql scripting.
This patch adds nestable conditional blocks to psql. The control structure feature per se is complete, but the boolean expressions understood by \if and \elif are pretty primitive; basically, after variable substitution and backtick expansion, the result has to be "true" or "false" or one of the other standard spellings of a boolean value. But that's enough for many purposes, since you can always do the heavy lifting on the server side; and we can extend it later. Along the way, pay down some of the technical debt that had built up around psql/command.c: * Refactor exec_command() into a function per command, instead of being a 1500-line monstrosity. This makes the file noticeably longer because of repetitive function header/trailer overhead, but it seems much more readable. * Teach psql_get_variable() and psqlscanslash.l to suppress variable substitution and backtick expansion on the basis of the conditional stack state, thereby allowing removal of the OT_NO_EVAL kluge. * Fix the no-doubt-once-expedient hack of sometimes silently substituting mainloop.c's previous_buf for query_buf when calling HandleSlashCmds. (It's a bit remarkable that commands like \r worked at all with that.) Recall of a previous query is now done explicitly in the slash commands where that should happen. Corey Huinker, reviewed by Fabien Coelho, further hacking by me Discussion: https://postgr.es/m/CADkLM=c94OSRTnat=LX0ivNq4pxDNeoomFfYvBKM5N_xfmLtAA@mail.gmail.com
This commit is contained in:
parent
ffae6733db
commit
e984ef5861
@ -2063,6 +2063,95 @@ hello 10
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term>
|
||||||
|
<term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term>
|
||||||
|
<term><literal>\else</literal></term>
|
||||||
|
<term><literal>\endif</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This group of commands implements nestable conditional blocks.
|
||||||
|
A conditional block must begin with an <command>\if</command> and end
|
||||||
|
with an <command>\endif</command>. In between there may be any number
|
||||||
|
of <command>\elif</command> clauses, which may optionally be followed
|
||||||
|
by a single <command>\else</command> clause. Ordinary queries and
|
||||||
|
other types of backslash commands may (and usually do) appear between
|
||||||
|
the commands forming a conditional block.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The <command>\if</command> and <command>\elif</command> commands read
|
||||||
|
their argument(s) and evaluate them as a boolean expression. If the
|
||||||
|
expression yields <literal>true</literal> then processing continues
|
||||||
|
normally; otherwise, lines are skipped until a
|
||||||
|
matching <command>\elif</command>, <command>\else</command>,
|
||||||
|
or <command>\endif</command> is reached. Once
|
||||||
|
an <command>\if</command> or <command>\elif</command> test has
|
||||||
|
succeeded, the arguments of later <command>\elif</command> commands in
|
||||||
|
the same block are not evaluated but are treated as false. Lines
|
||||||
|
following an <command>\else</command> are processed only if no earlier
|
||||||
|
matching <command>\if</command> or <command>\elif</command> succeeded.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The <replaceable class="parameter">expression</replaceable> argument
|
||||||
|
of an <command>\if</command> or <command>\elif</command> command
|
||||||
|
is subject to variable interpolation and backquote expansion, just
|
||||||
|
like any other backslash command argument. After that it is evaluated
|
||||||
|
like the value of an on/off option variable. So a valid value
|
||||||
|
is any unambiguous case-insensitive match for one of:
|
||||||
|
<literal>true</literal>, <literal>false</literal>, <literal>1</literal>,
|
||||||
|
<literal>0</literal>, <literal>on</literal>, <literal>off</literal>,
|
||||||
|
<literal>yes</literal>, <literal>no</literal>. For example,
|
||||||
|
<literal>t</literal>, <literal>T</literal>, and <literal>tR</literal>
|
||||||
|
will all be considered to be <literal>true</literal>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Expressions that do not properly evaluate to true or false will
|
||||||
|
generate a warning and be treated as false.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Lines being skipped are parsed normally to identify queries and
|
||||||
|
backslash commands, but queries are not sent to the server, and
|
||||||
|
backslash commands other than conditionals
|
||||||
|
(<command>\if</command>, <command>\elif</command>,
|
||||||
|
<command>\else</command>, <command>\endif</command>) are
|
||||||
|
ignored. Conditional commands are checked only for valid nesting.
|
||||||
|
Variable references in skipped lines are not expanded, and backquote
|
||||||
|
expansion is not performed either.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
All the backslash commands of a given conditional block must appear in
|
||||||
|
the same source file. If EOF is reached on the main input file or an
|
||||||
|
<command>\include</command>-ed file before all local
|
||||||
|
<command>\if</command>-blocks have been closed,
|
||||||
|
then <application>psql</> will raise an error.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Here is an example:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
-- check for the existence of two separate records in the database and store
|
||||||
|
-- the results in separate psql variables
|
||||||
|
SELECT
|
||||||
|
EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
|
||||||
|
EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
|
||||||
|
\gset
|
||||||
|
\if :is_customer
|
||||||
|
SELECT * FROM customer WHERE customer_id = 123;
|
||||||
|
\elif :is_employee
|
||||||
|
\echo 'is not a customer but is an employee'
|
||||||
|
SELECT * FROM employee WHERE employee_id = 456;
|
||||||
|
\else
|
||||||
|
\if yes
|
||||||
|
\echo 'not a customer or employee'
|
||||||
|
\else
|
||||||
|
\echo 'this will never print'
|
||||||
|
\endif
|
||||||
|
\endif
|
||||||
|
</programlisting>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><literal>\l[+]</literal> or <literal>\list[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
|
<term><literal>\l[+]</literal> or <literal>\list[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -3715,7 +3804,8 @@ testdb=> <userinput>INSERT INTO my_table VALUES (:'content');</userinput>
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
In prompt 1 normally <literal>=</literal>,
|
In prompt 1 normally <literal>=</literal>,
|
||||||
but <literal>^</literal> if in single-line mode,
|
but <literal>@</literal> if the session is in an inactive branch of a
|
||||||
|
conditional block, or <literal>^</literal> if in single-line mode,
|
||||||
or <literal>!</literal> if the session is disconnected from the
|
or <literal>!</literal> if the session is disconnected from the
|
||||||
database (which can happen if <command>\connect</command> fails).
|
database (which can happen if <command>\connect</command> fails).
|
||||||
In prompt 2 <literal>%R</literal> is replaced by a character that
|
In prompt 2 <literal>%R</literal> is replaced by a character that
|
||||||
|
@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
|
|||||||
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
|
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
|
||||||
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
|
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
|
||||||
|
|
||||||
OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
|
OBJS= command.o common.o conditional.o copy.o crosstabview.o \
|
||||||
startup.o prompt.o variables.o large_obj.o describe.o \
|
describe.o help.o input.o large_obj.o mainloop.o \
|
||||||
crosstabview.o tab-complete.o \
|
prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
|
||||||
sql_help.o psqlscanslash.o \
|
tab-complete.o variables.o \
|
||||||
$(WIN32RES)
|
$(WIN32RES)
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "fe_utils/print.h"
|
#include "fe_utils/print.h"
|
||||||
#include "fe_utils/psqlscan.h"
|
#include "fe_utils/psqlscan.h"
|
||||||
|
#include "conditional.h"
|
||||||
|
|
||||||
|
|
||||||
typedef enum _backslashResult
|
typedef enum _backslashResult
|
||||||
@ -25,7 +26,9 @@ typedef enum _backslashResult
|
|||||||
|
|
||||||
|
|
||||||
extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
|
extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
|
||||||
PQExpBuffer query_buf);
|
ConditionalStack cstack,
|
||||||
|
PQExpBuffer query_buf,
|
||||||
|
PQExpBuffer previous_buf);
|
||||||
|
|
||||||
extern int process_file(char *filename, bool use_relative_path);
|
extern int process_file(char *filename, bool use_relative_path);
|
||||||
|
|
||||||
|
@ -121,7 +121,8 @@ setQFout(const char *fname)
|
|||||||
* (Failure in escaping should lead to returning NULL.)
|
* (Failure in escaping should lead to returning NULL.)
|
||||||
*
|
*
|
||||||
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
|
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
|
||||||
* psql currently doesn't use this.
|
* In psql, passthrough points to a ConditionalStack, which we check to
|
||||||
|
* determine whether variable expansion is allowed.
|
||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
psql_get_variable(const char *varname, bool escape, bool as_ident,
|
psql_get_variable(const char *varname, bool escape, bool as_ident,
|
||||||
@ -130,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident,
|
|||||||
char *result;
|
char *result;
|
||||||
const char *value;
|
const char *value;
|
||||||
|
|
||||||
|
/* In an inactive \if branch, suppress all variable substitutions */
|
||||||
|
if (passthrough && !conditional_active((ConditionalStack) passthrough))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
value = GetVariable(pset.vars, varname);
|
value = GetVariable(pset.vars, varname);
|
||||||
if (!value)
|
if (!value)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
153
src/bin/psql/conditional.c
Normal file
153
src/bin/psql/conditional.c
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* psql - the PostgreSQL interactive terminal
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000-2017, PostgreSQL Global Development Group
|
||||||
|
*
|
||||||
|
* src/bin/psql/conditional.c
|
||||||
|
*/
|
||||||
|
#include "postgres_fe.h"
|
||||||
|
|
||||||
|
#include "conditional.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create stack
|
||||||
|
*/
|
||||||
|
ConditionalStack
|
||||||
|
conditional_stack_create(void)
|
||||||
|
{
|
||||||
|
ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
|
||||||
|
|
||||||
|
cstack->head = NULL;
|
||||||
|
return cstack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* destroy stack
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
conditional_stack_destroy(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
while (conditional_stack_pop(cstack))
|
||||||
|
continue;
|
||||||
|
free(cstack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new conditional branch.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
conditional_stack_push(ConditionalStack cstack, ifState new_state)
|
||||||
|
{
|
||||||
|
IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
|
||||||
|
|
||||||
|
p->if_state = new_state;
|
||||||
|
p->query_len = -1;
|
||||||
|
p->paren_depth = -1;
|
||||||
|
p->next = cstack->head;
|
||||||
|
cstack->head = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Destroy the topmost conditional branch.
|
||||||
|
* Returns false if there was no branch to end.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
conditional_stack_pop(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
IfStackElem *p = cstack->head;
|
||||||
|
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
cstack->head = cstack->head->next;
|
||||||
|
free(p);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch the current state of the top of the stack.
|
||||||
|
*/
|
||||||
|
ifState
|
||||||
|
conditional_stack_peek(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
if (conditional_stack_empty(cstack))
|
||||||
|
return IFSTATE_NONE;
|
||||||
|
return cstack->head->if_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the state of the topmost branch.
|
||||||
|
* Returns false if there was no branch state to set.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
conditional_stack_poke(ConditionalStack cstack, ifState new_state)
|
||||||
|
{
|
||||||
|
if (conditional_stack_empty(cstack))
|
||||||
|
return false;
|
||||||
|
cstack->head->if_state = new_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True if there are no active \if-blocks.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
conditional_stack_empty(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
return cstack->head == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True if we should execute commands normally; that is, the current
|
||||||
|
* conditional branch is active, or there is no open \if block.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
conditional_active(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
ifState s = conditional_stack_peek(cstack);
|
||||||
|
|
||||||
|
return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save current query buffer length in topmost stack entry.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
conditional_stack_set_query_len(ConditionalStack cstack, int len)
|
||||||
|
{
|
||||||
|
Assert(!conditional_stack_empty(cstack));
|
||||||
|
cstack->head->query_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch last-recorded query buffer length from topmost stack entry.
|
||||||
|
* Will return -1 if no stack or it was never saved.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
conditional_stack_get_query_len(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
if (conditional_stack_empty(cstack))
|
||||||
|
return -1;
|
||||||
|
return cstack->head->query_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save current parenthesis nesting depth in topmost stack entry.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
|
||||||
|
{
|
||||||
|
Assert(!conditional_stack_empty(cstack));
|
||||||
|
cstack->head->paren_depth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch last-recorded parenthesis nesting depth from topmost stack entry.
|
||||||
|
* Will return -1 if no stack or it was never saved.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
conditional_stack_get_paren_depth(ConditionalStack cstack)
|
||||||
|
{
|
||||||
|
if (conditional_stack_empty(cstack))
|
||||||
|
return -1;
|
||||||
|
return cstack->head->paren_depth;
|
||||||
|
}
|
83
src/bin/psql/conditional.h
Normal file
83
src/bin/psql/conditional.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* psql - the PostgreSQL interactive terminal
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000-2017, PostgreSQL Global Development Group
|
||||||
|
*
|
||||||
|
* src/bin/psql/conditional.h
|
||||||
|
*/
|
||||||
|
#ifndef CONDITIONAL_H
|
||||||
|
#define CONDITIONAL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Possible states of a single level of \if block.
|
||||||
|
*/
|
||||||
|
typedef enum ifState
|
||||||
|
{
|
||||||
|
IFSTATE_NONE = 0, /* not currently in an \if block */
|
||||||
|
IFSTATE_TRUE, /* currently in an \if or \elif that is true
|
||||||
|
* and all parent branches (if any) are true */
|
||||||
|
IFSTATE_FALSE, /* currently in an \if or \elif that is false
|
||||||
|
* but no true branch has yet been seen, and
|
||||||
|
* all parent branches (if any) are true */
|
||||||
|
IFSTATE_IGNORED, /* currently in an \elif that follows a true
|
||||||
|
* branch, or the whole \if is a child of a
|
||||||
|
* false parent branch */
|
||||||
|
IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all
|
||||||
|
* parent branches (if any) are true */
|
||||||
|
IFSTATE_ELSE_FALSE /* currently in an \else that is false or
|
||||||
|
* ignored */
|
||||||
|
} ifState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The state of nested \ifs is stored in a stack.
|
||||||
|
*
|
||||||
|
* query_len is used to determine what accumulated text to throw away at the
|
||||||
|
* end of an inactive branch. (We could, perhaps, teach the lexer to not add
|
||||||
|
* stuff to the query buffer in the first place when inside an inactive branch;
|
||||||
|
* but that would be very invasive.) We also need to save and restore the
|
||||||
|
* lexer's parenthesis nesting depth when throwing away text. (We don't need
|
||||||
|
* to save and restore any of its other state, such as comment nesting depth,
|
||||||
|
* because a backslash command could never appear inside a comment or SQL
|
||||||
|
* literal.)
|
||||||
|
*/
|
||||||
|
typedef struct IfStackElem
|
||||||
|
{
|
||||||
|
ifState if_state; /* current state, see enum above */
|
||||||
|
int query_len; /* length of query_buf at last branch start */
|
||||||
|
int paren_depth; /* parenthesis depth at last branch start */
|
||||||
|
struct IfStackElem *next; /* next surrounding \if, if any */
|
||||||
|
} IfStackElem;
|
||||||
|
|
||||||
|
typedef struct ConditionalStackData
|
||||||
|
{
|
||||||
|
IfStackElem *head;
|
||||||
|
} ConditionalStackData;
|
||||||
|
|
||||||
|
typedef struct ConditionalStackData *ConditionalStack;
|
||||||
|
|
||||||
|
|
||||||
|
extern ConditionalStack conditional_stack_create(void);
|
||||||
|
|
||||||
|
extern void conditional_stack_destroy(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern void conditional_stack_push(ConditionalStack cstack, ifState new_state);
|
||||||
|
|
||||||
|
extern bool conditional_stack_pop(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern ifState conditional_stack_peek(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state);
|
||||||
|
|
||||||
|
extern bool conditional_stack_empty(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern bool conditional_active(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
|
||||||
|
|
||||||
|
extern int conditional_stack_get_query_len(ConditionalStack cstack);
|
||||||
|
|
||||||
|
extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth);
|
||||||
|
|
||||||
|
extern int conditional_stack_get_paren_depth(ConditionalStack cstack);
|
||||||
|
|
||||||
|
#endif /* CONDITIONAL_H */
|
@ -552,7 +552,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
|
|||||||
/* interactive input probably silly, but give one prompt anyway */
|
/* interactive input probably silly, but give one prompt anyway */
|
||||||
if (showprompt)
|
if (showprompt)
|
||||||
{
|
{
|
||||||
const char *prompt = get_prompt(PROMPT_COPY);
|
const char *prompt = get_prompt(PROMPT_COPY, NULL);
|
||||||
|
|
||||||
fputs(prompt, stdout);
|
fputs(prompt, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
@ -590,7 +590,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
|
|||||||
|
|
||||||
if (showprompt)
|
if (showprompt)
|
||||||
{
|
{
|
||||||
const char *prompt = get_prompt(PROMPT_COPY);
|
const char *prompt = get_prompt(PROMPT_COPY, NULL);
|
||||||
|
|
||||||
fputs(prompt, stdout);
|
fputs(prompt, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
@ -167,7 +167,7 @@ slashUsage(unsigned short int pager)
|
|||||||
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
||||||
* the USE_READLINE line even in builds without that.
|
* the USE_READLINE line even in builds without that.
|
||||||
*/
|
*/
|
||||||
output = PageOutput(113, pager ? &(pset.popt.topt) : NULL);
|
output = PageOutput(122, pager ? &(pset.popt.topt) : NULL);
|
||||||
|
|
||||||
fprintf(output, _("General\n"));
|
fprintf(output, _("General\n"));
|
||||||
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
|
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
|
||||||
@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
|
|||||||
fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n"));
|
fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n"));
|
||||||
fprintf(output, "\n");
|
fprintf(output, "\n");
|
||||||
|
|
||||||
|
fprintf(output, _("Conditional\n"));
|
||||||
|
fprintf(output, _(" \\if EXPR begin conditional block\n"));
|
||||||
|
fprintf(output, _(" \\elif EXPR alternative within current conditional block\n"));
|
||||||
|
fprintf(output, _(" \\else final alternative within current conditional block\n"));
|
||||||
|
fprintf(output, _(" \\endif end conditional block\n"));
|
||||||
|
fprintf(output, "\n");
|
||||||
|
|
||||||
fprintf(output, _("Informational\n"));
|
fprintf(output, _("Informational\n"));
|
||||||
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
|
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
|
||||||
fprintf(output, _(" \\d[S+] list tables, views, and sequences\n"));
|
fprintf(output, _(" \\d[S+] list tables, views, and sequences\n"));
|
||||||
|
@ -35,6 +35,7 @@ int
|
|||||||
MainLoop(FILE *source)
|
MainLoop(FILE *source)
|
||||||
{
|
{
|
||||||
PsqlScanState scan_state; /* lexer working state */
|
PsqlScanState scan_state; /* lexer working state */
|
||||||
|
ConditionalStack cond_stack; /* \if status stack */
|
||||||
volatile PQExpBuffer query_buf; /* buffer for query being accumulated */
|
volatile PQExpBuffer query_buf; /* buffer for query being accumulated */
|
||||||
volatile PQExpBuffer previous_buf; /* if there isn't anything in the new
|
volatile PQExpBuffer previous_buf; /* if there isn't anything in the new
|
||||||
* buffer yet, use this one for \e,
|
* buffer yet, use this one for \e,
|
||||||
@ -50,16 +51,15 @@ MainLoop(FILE *source)
|
|||||||
volatile promptStatus_t prompt_status = PROMPT_READY;
|
volatile promptStatus_t prompt_status = PROMPT_READY;
|
||||||
volatile int count_eof = 0;
|
volatile int count_eof = 0;
|
||||||
volatile bool die_on_error = false;
|
volatile bool die_on_error = false;
|
||||||
|
|
||||||
/* Save the prior command source */
|
|
||||||
FILE *prev_cmd_source;
|
FILE *prev_cmd_source;
|
||||||
bool prev_cmd_interactive;
|
bool prev_cmd_interactive;
|
||||||
uint64 prev_lineno;
|
uint64 prev_lineno;
|
||||||
|
|
||||||
/* Save old settings */
|
/* Save the prior command source */
|
||||||
prev_cmd_source = pset.cur_cmd_source;
|
prev_cmd_source = pset.cur_cmd_source;
|
||||||
prev_cmd_interactive = pset.cur_cmd_interactive;
|
prev_cmd_interactive = pset.cur_cmd_interactive;
|
||||||
prev_lineno = pset.lineno;
|
prev_lineno = pset.lineno;
|
||||||
|
/* pset.stmt_lineno does not need to be saved and restored */
|
||||||
|
|
||||||
/* Establish new source */
|
/* Establish new source */
|
||||||
pset.cur_cmd_source = source;
|
pset.cur_cmd_source = source;
|
||||||
@ -69,6 +69,8 @@ MainLoop(FILE *source)
|
|||||||
|
|
||||||
/* Create working state */
|
/* Create working state */
|
||||||
scan_state = psql_scan_create(&psqlscan_callbacks);
|
scan_state = psql_scan_create(&psqlscan_callbacks);
|
||||||
|
cond_stack = conditional_stack_create();
|
||||||
|
psql_scan_set_passthrough(scan_state, (void *) cond_stack);
|
||||||
|
|
||||||
query_buf = createPQExpBuffer();
|
query_buf = createPQExpBuffer();
|
||||||
previous_buf = createPQExpBuffer();
|
previous_buf = createPQExpBuffer();
|
||||||
@ -122,7 +124,19 @@ MainLoop(FILE *source)
|
|||||||
cancel_pressed = false;
|
cancel_pressed = false;
|
||||||
|
|
||||||
if (pset.cur_cmd_interactive)
|
if (pset.cur_cmd_interactive)
|
||||||
|
{
|
||||||
putc('\n', stdout);
|
putc('\n', stdout);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if interactive user is in an \if block, then Ctrl-C will
|
||||||
|
* exit from the innermost \if.
|
||||||
|
*/
|
||||||
|
if (!conditional_stack_empty(cond_stack))
|
||||||
|
{
|
||||||
|
psql_error("\\if: escaped\n");
|
||||||
|
conditional_stack_pop(cond_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
successResult = EXIT_USER;
|
successResult = EXIT_USER;
|
||||||
@ -140,7 +154,8 @@ MainLoop(FILE *source)
|
|||||||
/* May need to reset prompt, eg after \r command */
|
/* May need to reset prompt, eg after \r command */
|
||||||
if (query_buf->len == 0)
|
if (query_buf->len == 0)
|
||||||
prompt_status = PROMPT_READY;
|
prompt_status = PROMPT_READY;
|
||||||
line = gets_interactive(get_prompt(prompt_status), query_buf);
|
line = gets_interactive(get_prompt(prompt_status, cond_stack),
|
||||||
|
query_buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -286,8 +301,10 @@ MainLoop(FILE *source)
|
|||||||
(scan_result == PSCAN_EOL && pset.singleline))
|
(scan_result == PSCAN_EOL && pset.singleline))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Save query in history. We use history_buf to accumulate
|
* Save line in history. We use history_buf to accumulate
|
||||||
* multi-line queries into a single history entry.
|
* 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)
|
if (pset.cur_cmd_interactive && !line_saved_in_history)
|
||||||
{
|
{
|
||||||
@ -296,7 +313,9 @@ MainLoop(FILE *source)
|
|||||||
line_saved_in_history = true;
|
line_saved_in_history = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* execute query */
|
/* execute query unless we're in an inactive \if branch */
|
||||||
|
if (conditional_active(cond_stack))
|
||||||
|
{
|
||||||
success = SendQuery(query_buf->data);
|
success = SendQuery(query_buf->data);
|
||||||
slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
|
slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
|
||||||
pset.stmt_lineno = 1;
|
pset.stmt_lineno = 1;
|
||||||
@ -313,6 +332,18 @@ MainLoop(FILE *source)
|
|||||||
added_nl_pos = -1;
|
added_nl_pos = -1;
|
||||||
/* we need not do psql_scan_reset() here */
|
/* we need not do psql_scan_reset() here */
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* if interactive, warn about non-executed query */
|
||||||
|
if (pset.cur_cmd_interactive)
|
||||||
|
psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
|
||||||
|
/* fake an OK result for purposes of loop checks */
|
||||||
|
success = true;
|
||||||
|
slashCmdStatus = PSQL_CMD_SEND;
|
||||||
|
pset.stmt_lineno = 1;
|
||||||
|
/* note that query_buf doesn't change state */
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (scan_result == PSCAN_BACKSLASH)
|
else if (scan_result == PSCAN_BACKSLASH)
|
||||||
{
|
{
|
||||||
/* handle backslash command */
|
/* handle backslash command */
|
||||||
@ -343,21 +374,24 @@ MainLoop(FILE *source)
|
|||||||
|
|
||||||
/* execute backslash command */
|
/* execute backslash command */
|
||||||
slashCmdStatus = HandleSlashCmds(scan_state,
|
slashCmdStatus = HandleSlashCmds(scan_state,
|
||||||
query_buf->len > 0 ?
|
cond_stack,
|
||||||
query_buf : previous_buf);
|
query_buf,
|
||||||
|
previous_buf);
|
||||||
|
|
||||||
success = slashCmdStatus != PSQL_CMD_ERROR;
|
success = slashCmdStatus != PSQL_CMD_ERROR;
|
||||||
pset.stmt_lineno = 1;
|
|
||||||
|
|
||||||
if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
|
/*
|
||||||
query_buf->len == 0)
|
* Resetting stmt_lineno after a backslash command isn't
|
||||||
{
|
* always appropriate, but it's what we've done historically
|
||||||
/* copy previous buffer to current for handling */
|
* and there have been few complaints.
|
||||||
appendPQExpBufferStr(query_buf, previous_buf->data);
|
*/
|
||||||
}
|
pset.stmt_lineno = 1;
|
||||||
|
|
||||||
if (slashCmdStatus == PSQL_CMD_SEND)
|
if (slashCmdStatus == PSQL_CMD_SEND)
|
||||||
{
|
{
|
||||||
|
/* should not see this in inactive branch */
|
||||||
|
Assert(conditional_active(cond_stack));
|
||||||
|
|
||||||
success = SendQuery(query_buf->data);
|
success = SendQuery(query_buf->data);
|
||||||
|
|
||||||
/* transfer query to previous_buf by pointer-swapping */
|
/* transfer query to previous_buf by pointer-swapping */
|
||||||
@ -374,6 +408,8 @@ MainLoop(FILE *source)
|
|||||||
}
|
}
|
||||||
else if (slashCmdStatus == PSQL_CMD_NEWEDIT)
|
else if (slashCmdStatus == PSQL_CMD_NEWEDIT)
|
||||||
{
|
{
|
||||||
|
/* should not see this in inactive branch */
|
||||||
|
Assert(conditional_active(cond_stack));
|
||||||
/* rescan query_buf as new input */
|
/* rescan query_buf as new input */
|
||||||
psql_scan_finish(scan_state);
|
psql_scan_finish(scan_state);
|
||||||
free(line);
|
free(line);
|
||||||
@ -429,8 +465,17 @@ MainLoop(FILE *source)
|
|||||||
if (pset.cur_cmd_interactive)
|
if (pset.cur_cmd_interactive)
|
||||||
pg_send_history(history_buf);
|
pg_send_history(history_buf);
|
||||||
|
|
||||||
/* execute query */
|
/* execute query unless we're in an inactive \if branch */
|
||||||
|
if (conditional_active(cond_stack))
|
||||||
|
{
|
||||||
success = SendQuery(query_buf->data);
|
success = SendQuery(query_buf->data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pset.cur_cmd_interactive)
|
||||||
|
psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!success && die_on_error)
|
if (!success && die_on_error)
|
||||||
successResult = EXIT_USER;
|
successResult = EXIT_USER;
|
||||||
@ -438,6 +483,19 @@ MainLoop(FILE *source)
|
|||||||
successResult = EXIT_BADCONN;
|
successResult = EXIT_BADCONN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for unbalanced \if-\endifs unless user explicitly quit, or the
|
||||||
|
* script is erroring out
|
||||||
|
*/
|
||||||
|
if (slashCmdStatus != PSQL_CMD_TERMINATE &&
|
||||||
|
successResult != EXIT_USER &&
|
||||||
|
!conditional_stack_empty(cond_stack))
|
||||||
|
{
|
||||||
|
psql_error("reached EOF without finding closing \\endif(s)\n");
|
||||||
|
if (die_on_error && !pset.cur_cmd_interactive)
|
||||||
|
successResult = EXIT_USER;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Let's just make real sure the SIGINT handler won't try to use
|
* 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
|
* sigint_interrupt_jmp after we exit this routine. If there is an outer
|
||||||
@ -452,6 +510,7 @@ MainLoop(FILE *source)
|
|||||||
destroyPQExpBuffer(history_buf);
|
destroyPQExpBuffer(history_buf);
|
||||||
|
|
||||||
psql_scan_destroy(scan_state);
|
psql_scan_destroy(scan_state);
|
||||||
|
conditional_stack_destroy(cond_stack);
|
||||||
|
|
||||||
pset.cur_cmd_source = prev_cmd_source;
|
pset.cur_cmd_source = prev_cmd_source;
|
||||||
pset.cur_cmd_interactive = prev_cmd_interactive;
|
pset.cur_cmd_interactive = prev_cmd_interactive;
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
char *
|
char *
|
||||||
get_prompt(promptStatus_t status)
|
get_prompt(promptStatus_t status, ConditionalStack cstack)
|
||||||
{
|
{
|
||||||
#define MAX_PROMPT_SIZE 256
|
#define MAX_PROMPT_SIZE 256
|
||||||
static char destination[MAX_PROMPT_SIZE + 1];
|
static char destination[MAX_PROMPT_SIZE + 1];
|
||||||
@ -188,7 +188,9 @@ get_prompt(promptStatus_t status)
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case PROMPT_READY:
|
case PROMPT_READY:
|
||||||
if (!pset.db)
|
if (cstack != NULL && !conditional_active(cstack))
|
||||||
|
buf[0] = '@';
|
||||||
|
else if (!pset.db)
|
||||||
buf[0] = '!';
|
buf[0] = '!';
|
||||||
else if (!pset.singleline)
|
else if (!pset.singleline)
|
||||||
buf[0] = '=';
|
buf[0] = '=';
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
/* enum promptStatus_t is now defined by psqlscan.h */
|
/* enum promptStatus_t is now defined by psqlscan.h */
|
||||||
#include "fe_utils/psqlscan.h"
|
#include "fe_utils/psqlscan.h"
|
||||||
|
#include "conditional.h"
|
||||||
|
|
||||||
char *get_prompt(promptStatus_t status);
|
char *get_prompt(promptStatus_t status, ConditionalStack cstack);
|
||||||
|
|
||||||
#endif /* PROMPT_H */
|
#endif /* PROMPT_H */
|
||||||
|
@ -18,8 +18,7 @@ enum slash_option_type
|
|||||||
OT_SQLID, /* treat as SQL identifier */
|
OT_SQLID, /* treat as SQL identifier */
|
||||||
OT_SQLIDHACK, /* SQL identifier, but don't downcase */
|
OT_SQLIDHACK, /* SQL identifier, but don't downcase */
|
||||||
OT_FILEPIPE, /* it's a filename or pipe */
|
OT_FILEPIPE, /* it's a filename or pipe */
|
||||||
OT_WHOLE_LINE, /* just snarf the rest of the line */
|
OT_WHOLE_LINE /* just snarf the rest of the line */
|
||||||
OT_NO_EVAL /* no expansion of backticks or variables */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -32,6 +31,10 @@ extern char *psql_scan_slash_option(PsqlScanState state,
|
|||||||
|
|
||||||
extern void psql_scan_slash_command_end(PsqlScanState state);
|
extern void psql_scan_slash_command_end(PsqlScanState state);
|
||||||
|
|
||||||
|
extern int psql_scan_get_paren_depth(PsqlScanState state);
|
||||||
|
|
||||||
|
extern void psql_scan_set_paren_depth(PsqlScanState state, int depth);
|
||||||
|
|
||||||
extern void dequote_downcase_identifier(char *str, bool downcase, int encoding);
|
extern void dequote_downcase_identifier(char *str, bool downcase, int encoding);
|
||||||
|
|
||||||
#endif /* PSQLSCANSLASH_H */
|
#endif /* PSQLSCANSLASH_H */
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "postgres_fe.h"
|
#include "postgres_fe.h"
|
||||||
|
|
||||||
#include "psqlscanslash.h"
|
#include "psqlscanslash.h"
|
||||||
|
#include "conditional.h"
|
||||||
|
|
||||||
#include "libpq-fe.h"
|
#include "libpq-fe.h"
|
||||||
}
|
}
|
||||||
@ -230,8 +231,7 @@ other .
|
|||||||
|
|
||||||
:{variable_char}+ {
|
:{variable_char}+ {
|
||||||
/* Possible psql variable substitution */
|
/* Possible psql variable substitution */
|
||||||
if (option_type == OT_NO_EVAL ||
|
if (cur_state->callbacks->get_variable == NULL)
|
||||||
cur_state->callbacks->get_variable == NULL)
|
|
||||||
ECHO;
|
ECHO;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -268,25 +268,15 @@ other .
|
|||||||
}
|
}
|
||||||
|
|
||||||
:'{variable_char}+' {
|
:'{variable_char}+' {
|
||||||
if (option_type == OT_NO_EVAL)
|
|
||||||
ECHO;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
psqlscan_escape_variable(cur_state, yytext, yyleng, false);
|
psqlscan_escape_variable(cur_state, yytext, yyleng, false);
|
||||||
*option_quote = ':';
|
*option_quote = ':';
|
||||||
}
|
|
||||||
unquoted_option_chars = 0;
|
unquoted_option_chars = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:\"{variable_char}+\" {
|
:\"{variable_char}+\" {
|
||||||
if (option_type == OT_NO_EVAL)
|
|
||||||
ECHO;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
psqlscan_escape_variable(cur_state, yytext, yyleng, true);
|
psqlscan_escape_variable(cur_state, yytext, yyleng, true);
|
||||||
*option_quote = ':';
|
*option_quote = ':';
|
||||||
}
|
|
||||||
unquoted_option_chars = 0;
|
unquoted_option_chars = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,8 +343,9 @@ other .
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
"`" {
|
"`" {
|
||||||
/* In NO_EVAL mode, don't evaluate the command */
|
/* In an inactive \if branch, don't evaluate the command */
|
||||||
if (option_type != OT_NO_EVAL)
|
if (cur_state->cb_passthrough == NULL ||
|
||||||
|
conditional_active((ConditionalStack) cur_state->cb_passthrough))
|
||||||
evaluate_backtick(cur_state);
|
evaluate_backtick(cur_state);
|
||||||
BEGIN(xslasharg);
|
BEGIN(xslasharg);
|
||||||
}
|
}
|
||||||
@ -641,6 +632,25 @@ psql_scan_slash_command_end(PsqlScanState state)
|
|||||||
psql_scan_reselect_sql_lexer(state);
|
psql_scan_reselect_sql_lexer(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fetch current paren nesting depth
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
psql_scan_get_paren_depth(PsqlScanState state)
|
||||||
|
{
|
||||||
|
return state->paren_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set paren nesting depth
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
psql_scan_set_paren_depth(PsqlScanState state, int depth)
|
||||||
|
{
|
||||||
|
Assert(depth >= 0);
|
||||||
|
state->paren_depth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* De-quote and optionally downcase a SQL identifier.
|
* De-quote and optionally downcase a SQL identifier.
|
||||||
*
|
*
|
||||||
|
@ -331,6 +331,7 @@ main(int argc, char *argv[])
|
|||||||
else if (cell->action == ACT_SINGLE_SLASH)
|
else if (cell->action == ACT_SINGLE_SLASH)
|
||||||
{
|
{
|
||||||
PsqlScanState scan_state;
|
PsqlScanState scan_state;
|
||||||
|
ConditionalStack cond_stack;
|
||||||
|
|
||||||
if (pset.echo == PSQL_ECHO_ALL)
|
if (pset.echo == PSQL_ECHO_ALL)
|
||||||
puts(cell->val);
|
puts(cell->val);
|
||||||
@ -339,11 +340,17 @@ main(int argc, char *argv[])
|
|||||||
psql_scan_setup(scan_state,
|
psql_scan_setup(scan_state,
|
||||||
cell->val, strlen(cell->val),
|
cell->val, strlen(cell->val),
|
||||||
pset.encoding, standard_strings());
|
pset.encoding, standard_strings());
|
||||||
|
cond_stack = conditional_stack_create();
|
||||||
|
psql_scan_set_passthrough(scan_state, (void *) cond_stack);
|
||||||
|
|
||||||
successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
|
successResult = HandleSlashCmds(scan_state,
|
||||||
|
cond_stack,
|
||||||
|
NULL,
|
||||||
|
NULL) != PSQL_CMD_ERROR
|
||||||
? EXIT_SUCCESS : EXIT_FAILURE;
|
? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
|
||||||
psql_scan_destroy(scan_state);
|
psql_scan_destroy(scan_state);
|
||||||
|
conditional_stack_destroy(cond_stack);
|
||||||
}
|
}
|
||||||
else if (cell->action == ACT_FILE)
|
else if (cell->action == ACT_FILE)
|
||||||
{
|
{
|
||||||
|
@ -2735,6 +2735,175 @@ deallocate q;
|
|||||||
\pset format aligned
|
\pset format aligned
|
||||||
\pset expanded off
|
\pset expanded off
|
||||||
\pset border 1
|
\pset border 1
|
||||||
|
-- tests for \if ... \endif
|
||||||
|
\if true
|
||||||
|
select 'okay';
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
okay
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select 'still okay';
|
||||||
|
?column?
|
||||||
|
------------
|
||||||
|
still okay
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\else
|
||||||
|
not okay;
|
||||||
|
still not okay
|
||||||
|
\endif
|
||||||
|
-- at this point query buffer should still have last valid line
|
||||||
|
\g
|
||||||
|
?column?
|
||||||
|
------------
|
||||||
|
still okay
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- \if should work okay on part of a query
|
||||||
|
select
|
||||||
|
\if true
|
||||||
|
42
|
||||||
|
\else
|
||||||
|
(bogus
|
||||||
|
\endif
|
||||||
|
forty_two;
|
||||||
|
forty_two
|
||||||
|
-----------
|
||||||
|
42
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
|
||||||
|
forty_two
|
||||||
|
-----------
|
||||||
|
42
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- test a large nested if using a variety of true-equivalents
|
||||||
|
\if true
|
||||||
|
\if 1
|
||||||
|
\if yes
|
||||||
|
\if on
|
||||||
|
\echo 'all true'
|
||||||
|
all true
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-1'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-2'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-3'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-4'
|
||||||
|
\endif
|
||||||
|
-- test a variety of false-equivalents in an if/elif/else structure
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #2-1'
|
||||||
|
\elif 0
|
||||||
|
\echo 'should not print #2-2'
|
||||||
|
\elif no
|
||||||
|
\echo 'should not print #2-3'
|
||||||
|
\elif off
|
||||||
|
\echo 'should not print #2-4'
|
||||||
|
\else
|
||||||
|
\echo 'all false'
|
||||||
|
all false
|
||||||
|
\endif
|
||||||
|
-- test simple true-then-else
|
||||||
|
\if true
|
||||||
|
\echo 'first thing true'
|
||||||
|
first thing true
|
||||||
|
\else
|
||||||
|
\echo 'should not print #3-1'
|
||||||
|
\endif
|
||||||
|
-- test simple false-true-else
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #4-1'
|
||||||
|
\elif true
|
||||||
|
\echo 'second thing true'
|
||||||
|
second thing true
|
||||||
|
\else
|
||||||
|
\echo 'should not print #5-1'
|
||||||
|
\endif
|
||||||
|
-- invalid boolean expressions are false
|
||||||
|
\if invalid boolean expression
|
||||||
|
unrecognized value "invalid boolean expression" for "\if expression": boolean expected
|
||||||
|
\echo 'will not print #6-1'
|
||||||
|
\else
|
||||||
|
\echo 'will print anyway #6-2'
|
||||||
|
will print anyway #6-2
|
||||||
|
\endif
|
||||||
|
-- test un-matched endif
|
||||||
|
\endif
|
||||||
|
\endif: no matching \if
|
||||||
|
-- test un-matched else
|
||||||
|
\else
|
||||||
|
\else: no matching \if
|
||||||
|
-- test un-matched elif
|
||||||
|
\elif
|
||||||
|
\elif: no matching \if
|
||||||
|
-- test double-else error
|
||||||
|
\if true
|
||||||
|
\else
|
||||||
|
\else
|
||||||
|
\else: cannot occur after \else
|
||||||
|
\endif
|
||||||
|
-- test elif out-of-order
|
||||||
|
\if false
|
||||||
|
\else
|
||||||
|
\elif
|
||||||
|
\elif: cannot occur after \else
|
||||||
|
\endif
|
||||||
|
-- test if-endif matching in a false branch
|
||||||
|
\if false
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #7-1'
|
||||||
|
\else
|
||||||
|
\echo 'should not print #7-2'
|
||||||
|
\endif
|
||||||
|
\echo 'should not print #7-3'
|
||||||
|
\else
|
||||||
|
\echo 'should print #7-4'
|
||||||
|
should print #7-4
|
||||||
|
\endif
|
||||||
|
-- show that vars and backticks are not expanded when ignoring extra args
|
||||||
|
\set foo bar
|
||||||
|
\echo :foo :'foo' :"foo"
|
||||||
|
bar 'bar' "bar"
|
||||||
|
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
\pset: extra argument "nosuchcommand" ignored
|
||||||
|
\pset: extra argument ":foo" ignored
|
||||||
|
\pset: extra argument ":'foo'" ignored
|
||||||
|
\pset: extra argument ":"foo"" ignored
|
||||||
|
-- show that vars and backticks are not expanded and commands are ignored
|
||||||
|
-- when in a false if-branch
|
||||||
|
\set try_to_quit '\\q'
|
||||||
|
\if false
|
||||||
|
:try_to_quit
|
||||||
|
\echo `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
|
||||||
|
\copy arg1 arg2 arg3 arg4 arg5 arg6
|
||||||
|
\copyright \dt arg1 \e arg1 arg2
|
||||||
|
\ef whole_line
|
||||||
|
\ev whole_line
|
||||||
|
\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
|
||||||
|
\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
|
||||||
|
\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
|
||||||
|
\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
|
||||||
|
\sf whole_line
|
||||||
|
\sv whole_line
|
||||||
|
\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
|
||||||
|
-- \else here is eaten as part of OT_FILEPIPE argument
|
||||||
|
\w |/no/such/file \else
|
||||||
|
-- \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
|
-- SHOW_CONTEXT
|
||||||
\set SHOW_CONTEXT never
|
\set SHOW_CONTEXT never
|
||||||
do $$
|
do $$
|
||||||
|
@ -382,6 +382,150 @@ deallocate q;
|
|||||||
\pset expanded off
|
\pset expanded off
|
||||||
\pset border 1
|
\pset border 1
|
||||||
|
|
||||||
|
-- tests for \if ... \endif
|
||||||
|
|
||||||
|
\if true
|
||||||
|
select 'okay';
|
||||||
|
select 'still okay';
|
||||||
|
\else
|
||||||
|
not okay;
|
||||||
|
still not okay
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- at this point query buffer should still have last valid line
|
||||||
|
\g
|
||||||
|
|
||||||
|
-- \if should work okay on part of a query
|
||||||
|
select
|
||||||
|
\if true
|
||||||
|
42
|
||||||
|
\else
|
||||||
|
(bogus
|
||||||
|
\endif
|
||||||
|
forty_two;
|
||||||
|
|
||||||
|
select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
|
||||||
|
|
||||||
|
-- test a large nested if using a variety of true-equivalents
|
||||||
|
\if true
|
||||||
|
\if 1
|
||||||
|
\if yes
|
||||||
|
\if on
|
||||||
|
\echo 'all true'
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-1'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-2'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-3'
|
||||||
|
\endif
|
||||||
|
\else
|
||||||
|
\echo 'should not print #1-4'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test a variety of false-equivalents in an if/elif/else structure
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #2-1'
|
||||||
|
\elif 0
|
||||||
|
\echo 'should not print #2-2'
|
||||||
|
\elif no
|
||||||
|
\echo 'should not print #2-3'
|
||||||
|
\elif off
|
||||||
|
\echo 'should not print #2-4'
|
||||||
|
\else
|
||||||
|
\echo 'all false'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test simple true-then-else
|
||||||
|
\if true
|
||||||
|
\echo 'first thing true'
|
||||||
|
\else
|
||||||
|
\echo 'should not print #3-1'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test simple false-true-else
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #4-1'
|
||||||
|
\elif true
|
||||||
|
\echo 'second thing true'
|
||||||
|
\else
|
||||||
|
\echo 'should not print #5-1'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- invalid boolean expressions are false
|
||||||
|
\if invalid boolean expression
|
||||||
|
\echo 'will not print #6-1'
|
||||||
|
\else
|
||||||
|
\echo 'will print anyway #6-2'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test un-matched endif
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test un-matched else
|
||||||
|
\else
|
||||||
|
|
||||||
|
-- test un-matched elif
|
||||||
|
\elif
|
||||||
|
|
||||||
|
-- test double-else error
|
||||||
|
\if true
|
||||||
|
\else
|
||||||
|
\else
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test elif out-of-order
|
||||||
|
\if false
|
||||||
|
\else
|
||||||
|
\elif
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- test if-endif matching in a false branch
|
||||||
|
\if false
|
||||||
|
\if false
|
||||||
|
\echo 'should not print #7-1'
|
||||||
|
\else
|
||||||
|
\echo 'should not print #7-2'
|
||||||
|
\endif
|
||||||
|
\echo 'should not print #7-3'
|
||||||
|
\else
|
||||||
|
\echo 'should print #7-4'
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- show that vars and backticks are not expanded when ignoring extra args
|
||||||
|
\set foo bar
|
||||||
|
\echo :foo :'foo' :"foo"
|
||||||
|
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
|
||||||
|
-- show that vars and backticks are not expanded and commands are ignored
|
||||||
|
-- when in a false if-branch
|
||||||
|
\set try_to_quit '\\q'
|
||||||
|
\if false
|
||||||
|
:try_to_quit
|
||||||
|
\echo `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
|
||||||
|
\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
|
||||||
|
\copy arg1 arg2 arg3 arg4 arg5 arg6
|
||||||
|
\copyright \dt arg1 \e arg1 arg2
|
||||||
|
\ef whole_line
|
||||||
|
\ev whole_line
|
||||||
|
\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
|
||||||
|
\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
|
||||||
|
\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
|
||||||
|
\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
|
||||||
|
\sf whole_line
|
||||||
|
\sv whole_line
|
||||||
|
\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
|
||||||
|
-- \else here is eaten as part of OT_FILEPIPE argument
|
||||||
|
\w |/no/such/file \else
|
||||||
|
-- \endif here is eaten as part of whole-line argument
|
||||||
|
\! whole_line \endif
|
||||||
|
\else
|
||||||
|
\echo 'should print #8-1'
|
||||||
|
\endif
|
||||||
|
|
||||||
-- SHOW_CONTEXT
|
-- SHOW_CONTEXT
|
||||||
|
|
||||||
\set SHOW_CONTEXT never
|
\set SHOW_CONTEXT never
|
||||||
|
Loading…
x
Reference in New Issue
Block a user