Skip to content

Commit e984ef5

Browse files
committed
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
1 parent ffae673 commit e984ef5

File tree

17 files changed

+2172
-270
lines changed

17 files changed

+2172
-270
lines changed

doc/src/sgml/ref/psql-ref.sgml

+91-1
Original file line numberDiff line numberDiff line change
@@ -2063,6 +2063,95 @@ hello 10
20632063
</varlistentry>
20642064

20652065

2066+
<varlistentry>
2067+
<term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term>
2068+
<term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term>
2069+
<term><literal>\else</literal></term>
2070+
<term><literal>\endif</literal></term>
2071+
<listitem>
2072+
<para>
2073+
This group of commands implements nestable conditional blocks.
2074+
A conditional block must begin with an <command>\if</command> and end
2075+
with an <command>\endif</command>. In between there may be any number
2076+
of <command>\elif</command> clauses, which may optionally be followed
2077+
by a single <command>\else</command> clause. Ordinary queries and
2078+
other types of backslash commands may (and usually do) appear between
2079+
the commands forming a conditional block.
2080+
</para>
2081+
<para>
2082+
The <command>\if</command> and <command>\elif</command> commands read
2083+
their argument(s) and evaluate them as a boolean expression. If the
2084+
expression yields <literal>true</literal> then processing continues
2085+
normally; otherwise, lines are skipped until a
2086+
matching <command>\elif</command>, <command>\else</command>,
2087+
or <command>\endif</command> is reached. Once
2088+
an <command>\if</command> or <command>\elif</command> test has
2089+
succeeded, the arguments of later <command>\elif</command> commands in
2090+
the same block are not evaluated but are treated as false. Lines
2091+
following an <command>\else</command> are processed only if no earlier
2092+
matching <command>\if</command> or <command>\elif</command> succeeded.
2093+
</para>
2094+
<para>
2095+
The <replaceable class="parameter">expression</replaceable> argument
2096+
of an <command>\if</command> or <command>\elif</command> command
2097+
is subject to variable interpolation and backquote expansion, just
2098+
like any other backslash command argument. After that it is evaluated
2099+
like the value of an on/off option variable. So a valid value
2100+
is any unambiguous case-insensitive match for one of:
2101+
<literal>true</literal>, <literal>false</literal>, <literal>1</literal>,
2102+
<literal>0</literal>, <literal>on</literal>, <literal>off</literal>,
2103+
<literal>yes</literal>, <literal>no</literal>. For example,
2104+
<literal>t</literal>, <literal>T</literal>, and <literal>tR</literal>
2105+
will all be considered to be <literal>true</literal>.
2106+
</para>
2107+
<para>
2108+
Expressions that do not properly evaluate to true or false will
2109+
generate a warning and be treated as false.
2110+
</para>
2111+
<para>
2112+
Lines being skipped are parsed normally to identify queries and
2113+
backslash commands, but queries are not sent to the server, and
2114+
backslash commands other than conditionals
2115+
(<command>\if</command>, <command>\elif</command>,
2116+
<command>\else</command>, <command>\endif</command>) are
2117+
ignored. Conditional commands are checked only for valid nesting.
2118+
Variable references in skipped lines are not expanded, and backquote
2119+
expansion is not performed either.
2120+
</para>
2121+
<para>
2122+
All the backslash commands of a given conditional block must appear in
2123+
the same source file. If EOF is reached on the main input file or an
2124+
<command>\include</command>-ed file before all local
2125+
<command>\if</command>-blocks have been closed,
2126+
then <application>psql</> will raise an error.
2127+
</para>
2128+
<para>
2129+
Here is an example:
2130+
</para>
2131+
<programlisting>
2132+
-- check for the existence of two separate records in the database and store
2133+
-- the results in separate psql variables
2134+
SELECT
2135+
EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
2136+
EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
2137+
\gset
2138+
\if :is_customer
2139+
SELECT * FROM customer WHERE customer_id = 123;
2140+
\elif :is_employee
2141+
\echo 'is not a customer but is an employee'
2142+
SELECT * FROM employee WHERE employee_id = 456;
2143+
\else
2144+
\if yes
2145+
\echo 'not a customer or employee'
2146+
\else
2147+
\echo 'this will never print'
2148+
\endif
2149+
\endif
2150+
</programlisting>
2151+
</listitem>
2152+
</varlistentry>
2153+
2154+
20662155
<varlistentry>
20672156
<term><literal>\l[+]</literal> or <literal>\list[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
20682157
<listitem>
@@ -3715,7 +3804,8 @@ testdb=&gt; <userinput>INSERT INTO my_table VALUES (:'content');</userinput>
37153804
<listitem>
37163805
<para>
37173806
In prompt 1 normally <literal>=</literal>,
3718-
but <literal>^</literal> if in single-line mode,
3807+
but <literal>@</literal> if the session is in an inactive branch of a
3808+
conditional block, or <literal>^</literal> if in single-line mode,
37193809
or <literal>!</literal> if the session is disconnected from the
37203810
database (which can happen if <command>\connect</command> fails).
37213811
In prompt 2 <literal>%R</literal> is replaced by a character that

src/bin/psql/Makefile

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
2121
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
2222
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
2323

24-
OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
25-
startup.o prompt.o variables.o large_obj.o describe.o \
26-
crosstabview.o tab-complete.o \
27-
sql_help.o psqlscanslash.o \
24+
OBJS= command.o common.o conditional.o copy.o crosstabview.o \
25+
describe.o help.o input.o large_obj.o mainloop.o \
26+
prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
27+
tab-complete.o variables.o \
2828
$(WIN32RES)
2929

3030

0 commit comments

Comments
 (0)