Hello Teodor,
Patch seems usefull and commitable except comments in conditional.[ch]. I'd
like to top/header comment in each file more detailed and descriptive. As for
now it mentions only psql usage without explaining how it is basic or common.
Indeed, it was not updated.
I've fixed the file names and added a simple description at the beginning
of the header file, and a one liner in the code file.
Do you think that more is needed?
The patch also needed a rebase after the hash function addition.
--
Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f07ddf1..d52d324 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -900,6 +900,21 @@ pgbench <optional> <replaceable>options</replaceable>
</optional> <replaceable>d
</para>
<variablelist>
+ <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,
+ similarly to <literal>psql</literal>'s <xref
linkend="psql-metacommand-if"/>.
+ Conditional expressions are identical to those with
<literal>\set</literal>,
+ with non-zero values interpreted as true.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id='pgbench-metacommand-set'>
<term>
<literal>\set <replaceable>varname</replaceable>
<replaceable>expression</replaceable></literal>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index bfdf859..10b9795 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2169,7 +2169,7 @@ hello 10
</varlistentry>
- <varlistentry>
+ <varlistentry id="psql-metacommand-if">
<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>
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index a15aa06..894571e 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -32,6 +32,7 @@
#endif /* ! WIN32 */
#include "postgres_fe.h"
+#include "fe_utils/conditional.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -282,6 +283,9 @@ typedef enum
* and we enter the CSTATE_SLEEP state to wait for it to expire. Other
* meta-commands are executed immediately.
*
+ * CSTATE_SKIP_COMMAND for conditional branches which are not executed,
+ * quickly skip commands that do not need any evaluation.
+ *
* CSTATE_WAIT_RESULT waits until we get a result set back from the
server
* for the current command.
*
@@ -291,6 +295,7 @@ typedef enum
* command counter, and loops back to CSTATE_START_COMMAND state.
*/
CSTATE_START_COMMAND,
+ CSTATE_SKIP_COMMAND,
CSTATE_WAIT_RESULT,
CSTATE_SLEEP,
CSTATE_END_COMMAND,
@@ -320,6 +325,7 @@ typedef struct
PGconn *con; /* connection handle to DB */
int id; /* client No. */
ConnectionStateEnum state; /* state machine's current state. */
+ ConditionalStack cstack; /* enclosing conditionals state */
int use_file; /* index in sql_script
for this client */
int command; /* command number in
script */
@@ -408,7 +414,11 @@ typedef enum MetaCommand
META_SET, /* \set */
META_SETSHELL, /* \setshell */
META_SHELL, /* \shell */
- META_SLEEP /* \sleep */
+ META_SLEEP, /* \sleep */
+ META_IF, /* \if */
+ META_ELIF, /* \elif */
+ META_ELSE, /* \else */
+ META_ENDIF /* \endif */
} MetaCommand;
typedef enum QueryMode
@@ -1645,6 +1655,7 @@ setBoolValue(PgBenchValue *pv, bool bval)
pv->type = PGBT_BOOLEAN;
pv->u.bval = bval;
}
+
/* assign an integer value */
static void
setIntValue(PgBenchValue *pv, int64 ival)
@@ -2377,6 +2388,14 @@ getMetaCommand(const char *cmd)
mc = META_SHELL;
else if (pg_strcasecmp(cmd, "sleep") == 0)
mc = META_SLEEP;
+ else if (pg_strcasecmp(cmd, "if") == 0)
+ mc = META_IF;
+ else if (pg_strcasecmp(cmd, "elif") == 0)
+ mc = META_ELIF;
+ else if (pg_strcasecmp(cmd, "else") == 0)
+ mc = META_ELSE;
+ else if (pg_strcasecmp(cmd, "endif") == 0)
+ mc = META_ENDIF;
else
mc = META_NONE;
return mc;
@@ -2498,11 +2517,11 @@ preparedStatementName(char *buffer, int file, int state)
}
static void
-commandFailed(CState *st, const char *message)
+commandFailed(CState *st, const char *cmd, const char *message)
{
fprintf(stderr,
- "client %d aborted in command %d of script %d; %s\n",
- st->id, st->command, st->use_file, message);
+ "client %d aborted in command %d (%s) of script %d;
%s\n",
+ st->id, st->command, cmd, st->use_file, message);
}
/* return a script number with a weighted choice. */
@@ -2690,6 +2709,8 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_START_THROTTLE;
else
st->state = CSTATE_START_TX;
+ /* check consistency */
+ Assert(conditional_stack_empty(st->cstack));
break;
/*
@@ -2855,7 +2876,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
{
if (!sendCommand(st, command))
{
- commandFailed(st, "SQL command
send failed");
+ commandFailed(st, "SQL", "SQL
command send failed");
st->state = CSTATE_ABORTED;
}
else
@@ -2888,7 +2909,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
if (!evaluateSleep(st, argc,
argv, &usec))
{
- commandFailed(st,
"execution of meta-command 'sleep' failed");
+ commandFailed(st,
"sleep", "execution of meta-command failed");
st->state =
CSTATE_ABORTED;
break;
}
@@ -2899,77 +2920,209 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_SLEEP;
break;
}
- else
+ else if (command->meta == META_SET ||
+ command->meta ==
META_IF ||
+ command->meta ==
META_ELIF)
{
+ /* backslash commands with an
expression to evaluate */
+ PgBenchExpr *expr =
command->expr;
+ PgBenchValue result;
+
+ if (command->meta == META_ELIF
&&
+
conditional_stack_peek(st->cstack) == IFSTATE_TRUE)
+ {
+ /* elif after executed
block, skip eval and wait for endif */
+
conditional_stack_poke(st->cstack, IFSTATE_IGNORED);
+ goto
move_to_end_command;
+ }
+
+ if (!evaluateExpr(thread, st,
expr, &result))
+ {
+ commandFailed(st,
argv[0], "evaluation of meta-command failed");
+ st->state =
CSTATE_ABORTED;
+ break;
+ }
+
if (command->meta == META_SET)
{
- PgBenchExpr *expr =
command->expr;
- PgBenchValue result;
-
- if
(!evaluateExpr(thread, st, expr, &result))
- {
-
commandFailed(st, "evaluation of meta-command 'set' failed");
- st->state =
CSTATE_ABORTED;
- break;
- }
-
if
(!putVariableValue(st, argv[0], argv[1], &result))
{
-
commandFailed(st, "assignment of meta-command 'set' failed");
+
commandFailed(st, "set", "assignment of meta-command failed");
st->state =
CSTATE_ABORTED;
break;
}
}
- else if (command->meta ==
META_SETSHELL)
+ else /* if and elif evaluated
cases */
{
- bool ret =
runShellCommand(st, argv[1], argv + 2, argc - 2);
+ bool cond =
valueTruth(&result);
- if (timer_exceeded) /*
timeout */
+ /* execute or not
depending on evaluated condition */
+ if (command->meta ==
META_IF)
{
- st->state =
CSTATE_FINISHED;
- break;
- }
- else if (!ret) /* on
error */
- {
-
commandFailed(st, "execution of meta-command 'setshell' failed");
- st->state =
CSTATE_ABORTED;
- break;
+
conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
- else
+ else /* elif */
{
- /* succeeded */
+ /* we should
get here only if the "elif" needed evaluation */
+
Assert(conditional_stack_peek(st->cstack) == IFSTATE_FALSE);
+
conditional_stack_poke(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
}
- else if (command->meta ==
META_SHELL)
+ }
+ else if (command->meta == META_ELSE)
+ {
+ switch
(conditional_stack_peek(st->cstack))
+ {
+ case IFSTATE_TRUE:
+
conditional_stack_poke(st->cstack, IFSTATE_ELSE_FALSE);
+ break;
+ case IFSTATE_FALSE: /*
inconsistent if active */
+ case IFSTATE_IGNORED:
/* inconsistent if active */
+ case IFSTATE_NONE: /*
else without if */
+ case IFSTATE_ELSE_TRUE:
/* else after else */
+ case
IFSTATE_ELSE_FALSE: /* else after else */
+ default:
+ /* dead code if
conditional check is ok */
+ Assert(false);
+ }
+ goto move_to_end_command;
+ }
+ else if (command->meta == META_ENDIF)
+ {
+
Assert(!conditional_stack_empty(st->cstack));
+
conditional_stack_pop(st->cstack);
+ goto move_to_end_command;
+ }
+ else if (command->meta == META_SETSHELL)
+ {
+ bool ret =
runShellCommand(st, argv[1], argv + 2, argc - 2);
+
+ if (timer_exceeded) /* timeout
*/
+ {
+ st->state =
CSTATE_FINISHED;
+ break;
+ }
+ else if (!ret) /* on error */
+ {
+ commandFailed(st,
"setshell", "execution of meta-command failed");
+ st->state =
CSTATE_ABORTED;
+ break;
+ }
+ else
+ {
+ /* succeeded */
+ }
+ }
+ else if (command->meta == META_SHELL)
+ {
+ bool ret =
runShellCommand(st, NULL, argv + 1, argc - 1);
+
+ if (timer_exceeded) /* timeout
*/
+ {
+ st->state =
CSTATE_FINISHED;
+ break;
+ }
+ else if (!ret) /* on error */
{
- bool ret =
runShellCommand(st, NULL, argv + 1, argc - 1);
+ commandFailed(st,
"shell", "execution of meta-command failed");
+ st->state =
CSTATE_ABORTED;
+ break;
+ }
+ else
+ {
+ /* succeeded */
+ }
+ }
+
+ move_to_end_command:
+ /*
+ * executing the expression or shell
command might
+ * take a non-negligible amount of
time, so reset
+ * 'now'
+ */
+ INSTR_TIME_SET_ZERO(now);
+
+ st->state = CSTATE_END_COMMAND;
+ }
+ break;
+
+ /*
+ * non executed conditional branch
+ */
+ case CSTATE_SKIP_COMMAND:
+ Assert(!conditional_active(st->cstack));
+ /* quickly skip commands until something to
do... */
+ while (true)
+ {
+ command =
sql_script[st->use_file].commands[st->command];
+
+ /* cannot reach end of script in that
state */
+ Assert(command != NULL);
- if (timer_exceeded) /*
timeout */
+ /* if this is conditional related,
update conditional state */
+ if (command->type == META_COMMAND &&
+ (command->meta == META_IF ||
+ command->meta == META_ELIF ||
+ command->meta == META_ELSE ||
+ command->meta == META_ENDIF))
+ {
+ switch
(conditional_stack_peek(st->cstack))
+ {
+ case IFSTATE_FALSE:
+ if (command->meta ==
META_IF || command->meta == META_ELIF)
{
- st->state =
CSTATE_FINISHED;
- break;
+ /* we must
evaluate the condition */
+ st->state =
CSTATE_START_COMMAND;
}
- else if (!ret) /* on
error */
+ else if (command->meta
== META_ELSE)
{
-
commandFailed(st, "execution of meta-command 'shell' failed");
- st->state =
CSTATE_ABORTED;
- break;
+ /* we must
execute next command */
+
conditional_stack_poke(st->cstack, IFSTATE_ELSE_TRUE);
+ st->state =
CSTATE_START_COMMAND;
+ st->command++;
}
- else
+ else if (command->meta
== META_ENDIF)
{
- /* succeeded */
+
Assert(!conditional_stack_empty(st->cstack));
+
conditional_stack_pop(st->cstack);
+ if
(conditional_active(st->cstack))
+
st->state = CSTATE_START_COMMAND;
+ /* else state
remains in CSTATE_SKIP_COMMAND */
+ st->command++;
}
- }
+ break;
- /*
- * executing the expression or
shell command might
- * take a non-negligible amount
of time, so reset
- * 'now'
- */
- INSTR_TIME_SET_ZERO(now);
+ case IFSTATE_IGNORED:
+ case IFSTATE_ELSE_FALSE:
+ if (command->meta ==
META_IF)
+
conditional_stack_push(st->cstack, IFSTATE_IGNORED);
+ else if (command->meta
== META_ENDIF)
+ {
+
Assert(!conditional_stack_empty(st->cstack));
+
conditional_stack_pop(st->cstack);
+ if
(conditional_active(st->cstack))
+
st->state = CSTATE_START_COMMAND;
+ }
+ /* could detect "else"
& "elif" after "else" */
+ st->command++;
+ break;
- st->state = CSTATE_END_COMMAND;
+ case IFSTATE_NONE:
+ case IFSTATE_TRUE:
+ case IFSTATE_ELSE_TRUE:
+ default:
+ /* inconsistent if
inactive, unreachable dead code */
+ Assert(false);
+ }
}
+ else
+ {
+ /* skip and consider next */
+ st->command++;
+ }
+
+ if (st->state != CSTATE_SKIP_COMMAND)
+ break;
}
break;
@@ -2982,7 +3135,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
fprintf(stderr, "client %d
receiving\n", st->id);
if (!PQconsumeInput(st->con))
{ /* there's
something wrong */
- commandFailed(st, "perhaps the backend
died while processing");
+ commandFailed(st, "SQL", "perhaps the
backend died while processing");
st->state = CSTATE_ABORTED;
break;
}
@@ -3004,7 +3157,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_END_COMMAND;
break;
default:
- commandFailed(st,
PQerrorMessage(st->con));
+ commandFailed(st, "SQL",
PQerrorMessage(st->con));
PQclear(res);
st->state = CSTATE_ABORTED;
break;
@@ -3048,9 +3201,10 @@ doCustom(TState *thread, CState *st, StatsData *agg)
INSTR_TIME_GET_DOUBLE(st->stmt_begin));
}
- /* Go ahead with next command */
+ /* Go ahead with next command, to be executed
or skipped */
st->command++;
- st->state = CSTATE_START_COMMAND;
+ st->state = conditional_active(st->cstack) ?
+ CSTATE_START_COMMAND :
CSTATE_SKIP_COMMAND;
break;
/*
@@ -3061,6 +3215,13 @@ doCustom(TState *thread, CState *st, StatsData *agg)
/* transaction finished: calculate latency and
do log */
processXactStats(thread, st, &now, false, agg);
+ /* conditional stack must be empty */
+ if (!conditional_stack_empty(st->cstack))
+ {
+ fprintf(stderr, "end of script reached
within a conditional, missing \\endif\n");
+ exit(1);
+ }
+
if (is_connect)
{
finishCon(st);
@@ -3870,19 +4031,25 @@ process_backslash_command(PsqlScanState sstate, const
char *source)
/* ... and convert it to enum form */
my_command->meta = getMetaCommand(my_command->argv[0]);
- if (my_command->meta == META_SET)
+ if (my_command->meta == META_SET ||
+ my_command->meta == META_IF ||
+ my_command->meta == META_ELIF)
{
- /* For \set, collect var name, then lex the expression. */
yyscan_t yyscanner;
- if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
- syntax_error(source, lineno, my_command->line,
my_command->argv[0],
- "missing argument", NULL, -1);
+ /* For \set, collect var name */
+ if (my_command->meta == META_SET)
+ {
+ if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
+ syntax_error(source, lineno, my_command->line,
my_command->argv[0],
+ "missing argument",
NULL, -1);
- offsets[j] = word_offset;
- my_command->argv[j++] = pg_strdup(word_buf.data);
- my_command->argc++;
+ offsets[j] = word_offset;
+ my_command->argv[j++] = pg_strdup(word_buf.data);
+ my_command->argc++;
+ }
+ /* then for all parse the expression */
yyscanner = expr_scanner_init(sstate, source, lineno,
start_offset,
my_command->argv[0]);
@@ -3978,6 +4145,12 @@ process_backslash_command(PsqlScanState sstate, const
char *source)
syntax_error(source, lineno, my_command->line,
my_command->argv[0],
"missing command", NULL, -1);
}
+ else if (my_command->meta == META_ELSE || my_command->meta ==
META_ENDIF)
+ {
+ if (my_command->argc != 1)
+ syntax_error(source, lineno, my_command->line,
my_command->argv[0],
+ "unexpected argument", NULL,
-1);
+ }
else
{
/* my_command->meta == META_NONE */
@@ -3990,6 +4163,62 @@ process_backslash_command(PsqlScanState sstate, const
char *source)
return my_command;
}
+static void
+ConditionError(const char *desc, int cmdn, const char *msg)
+{
+ fprintf(stderr,
+ "condition error in script \"%s\" command %d: %s\n",
+ desc, cmdn, msg);
+ exit(1);
+}
+
+/*
+ * Partial evaluation of conditionals before recording and running the script.
+ */
+static void
+CheckConditional(ParsedScript ps)
+{
+ /* statically check conditional structure */
+ ConditionalStack cs = conditional_stack_create();
+ int i;
+ for (i = 0 ; ps.commands[i] != NULL ; i++)
+ {
+ Command *cmd = ps.commands[i];
+ if (cmd->type == META_COMMAND)
+ {
+ switch (cmd->meta)
+ {
+ case META_IF:
+ conditional_stack_push(cs, IFSTATE_FALSE);
+ break;
+ case META_ELIF:
+ if (conditional_stack_empty(cs))
+ ConditionError(ps.desc, i+1, "\\elif
without matching \\if");
+ if (conditional_stack_peek(cs) ==
IFSTATE_ELSE_FALSE)
+ ConditionError(ps.desc, i+1, "\\elif
after \\else");
+ break;
+ case META_ELSE:
+ if (conditional_stack_empty(cs))
+ ConditionError(ps.desc, i+1, "\\else
without matching \\if");
+ if (conditional_stack_peek(cs) ==
IFSTATE_ELSE_FALSE)
+ ConditionError(ps.desc, i+1, "\\else
after \\else");
+ conditional_stack_poke(cs, IFSTATE_ELSE_FALSE);
+ break;
+ case META_ENDIF:
+ if (!conditional_stack_pop(cs))
+ ConditionError(ps.desc, i+1, "\\endif
without matching \\if");
+ break;
+ default:
+ /* ignore anything else... */
+ break;
+ }
+ }
+ }
+ if (!conditional_stack_empty(cs))
+ ConditionError(ps.desc, i+1, "\\if without matching \\endif");
+ conditional_stack_destroy(cs);
+}
+
/*
* Parse a script (either the contents of a file, or a built-in script)
* and add it to the list of scripts.
@@ -4275,6 +4504,8 @@ addScript(ParsedScript script)
exit(1);
}
+ CheckConditional(script);
+
sql_script[num_scripts] = script;
num_scripts++;
}
@@ -5021,6 +5252,12 @@ main(int argc, char **argv)
}
}
+ /* other CState initializations */
+ for (i = 0; i < nclients; i++)
+ {
+ state[i].cstack = conditional_stack_create();
+ }
+
if (debug)
{
if (duration <= 0)
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl
b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 50cbb23..7448a96 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -264,6 +264,12 @@ pgbench(
qr{command=51.: int -7793829335365542153\b},
qr{command=52.: int -?\d+\b},
qr{command=53.: boolean true\b},
+ qr{command=65.: int 65\b},
+ qr{command=74.: int 74\b},
+ qr{command=83.: int 83\b},
+ qr{command=86.: int 86\b},
+ qr{command=93.: int 93\b},
+ qr{command=95.: int 0\b},
],
'pgbench expressions',
{ '001_pgbench_expressions' => q{-- integer functions
@@ -349,6 +355,41 @@ pgbench(
\set v2 5432
\set v3 -54.21E-2
SELECT :v0, :v1, :v2, :v3;
+-- if tests
+\set nope 0
+\if 1 > 0
+\set id debug(65)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 > 0
+\set ie debug(74)
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 < 0
+\set nope 1
+\else
+\set if debug(83)
+\endif
+\if 1 = 1
+\set ig debug(86)
+\elif 0
+\set nope 1
+\endif
+\if 1 = 0
+\set nope 1
+\elif 1 <> 0
+\set ih debug(93)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
} });
# backslash commands
@@ -396,7 +437,7 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
# SHELL
[ 'shell bad command', 0,
- [qr{meta-command 'shell' failed}], q{\shell no-such-command} ],
+ [qr{\(shell\) .* meta-command failed}], q{\shell
no-such-command} ],
[ 'shell undefined variable', 0,
[qr{undefined variable ":nosuchvariable"}],
q{-- undefined variable in shell
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl
b/src/bin/pgbench/t/002_pgbench_no_server.pl
index 6ea55f8..80c5aed 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -8,6 +8,16 @@ use warnings;
use TestLib;
use Test::More;
+# create a directory for scripts
+my $testname = $0;
+$testname =~ s,.*/,,;
+$testname =~ s/\.pl$//;
+
+my $testdir = "$TestLib::tmp_check/t_${testname}_stuff";
+mkdir $testdir
+ or
+ BAIL_OUT("could not create test directory \"${testdir}\": $!");
+
# invoke pgbench
sub pgbench
{
@@ -17,6 +27,28 @@ sub pgbench
$stat, $out, $err, $name);
}
+# invoke pgbench with scripts
+sub pgbench_scripts
+{
+ my ($opts, $stat, $out, $err, $name, $files) = @_;
+ my @cmd = ('pgbench', split /\s+/, $opts);
+ my @filenames = ();
+ if (defined $files)
+ {
+ for my $fn (sort keys %$files)
+ {
+ my $filename = $testdir . '/' . $fn;
+ # cleanup file weight if any
+ $filename =~ s/\@\d+$//;
+ # cleanup from prior runs
+ unlink $filename;
+ append_to_file($filename, $$files{$fn});
+ push @cmd, '-f', $filename;
+ }
+ }
+ command_checks_all(\@cmd, $stat, $out, $err, $name);
+}
+
#
# Option various errors
#
@@ -125,4 +157,24 @@ pgbench(
qr{simple-update}, qr{select-only} ],
'pgbench builtin list');
+my @script_tests = (
+ # name, err, { file => contents }
+ [ 'missing endif', [qr{\\if without matching \\endif}],
{'if-noendif.sql' => '\if 1'} ],
+ [ 'missing if on elif', [qr{\\elif without matching \\if}],
{'elif-noif.sql' => '\elif 1'} ],
+ [ 'missing if on else', [qr{\\else without matching \\if}],
{'else-noif.sql' => '\else'} ],
+ [ 'missing if on endif', [qr{\\endif without matching \\if}],
{'endif-noif.sql' => '\endif'} ],
+ [ 'elif after else', [qr{\\elif after \\else}], {'else-elif.sql' =>
"\\if 1\n\\else\n\\elif 0\n\\endif"} ],
+ [ 'else after else', [qr{\\else after \\else}], {'else-else.sql' =>
"\\if 1\n\\else\n\\else\n\\endif"} ],
+ [ 'if syntax error', [qr{syntax error in command "if"}], {'if-bad.sql'
=> "\\if\n\\endif\n"} ],
+ [ 'elif syntax error', [qr{syntax error in command "elif"}],
{'elif-bad.sql' => "\\if 0\n\\elif +\n\\endif\n"} ],
+ [ 'else syntax error', [qr{unexpected argument in command "else"}],
{'else-bad.sql' => "\\if 0\n\\else BAD\n\\endif\n"} ],
+ [ 'endif syntax error', [qr{unexpected argument in command "endif"}],
{'endif-bad.sql' => "\\if 0\n\\endif BAD\n"} ],
+);
+
+for my $t (@script_tests)
+{
+ my ($name, $err, $files) = @$t;
+ pgbench_scripts('', 1, [qr{^$}], $err, 'pgbench option error: ' .
$name, $files);
+}
+
done_testing();
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index c8eb2f9..b3166ec 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -21,7 +21,7 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
override LDFLAGS := -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
$(LDFLAGS)
-OBJS= command.o common.o conditional.o copy.o crosstabview.o \
+OBJS= command.o common.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 \
diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h
index 95ad02d..29a8edd 100644
--- a/src/bin/psql/command.h
+++ b/src/bin/psql/command.h
@@ -10,7 +10,7 @@
#include "fe_utils/print.h"
#include "fe_utils/psqlscan.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
typedef enum _backslashResult
diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c
deleted file mode 100644
index cebf8c7..0000000
--- a/src/bin/psql/conditional.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
- *
- * src/bin/psql/conditional.c
- */
-#include "postgres_fe.h"
-
-#include "conditional.h"
-
-/*
- * create stack
- */
-ConditionalStack
-conditional_stack_create(void)
-{
- ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
-
- cstack->head = NULL;
- return cstack;
-}
-
-/*
- * destroy stack
- */
-void
-conditional_stack_destroy(ConditionalStack cstack)
-{
- while (conditional_stack_pop(cstack))
- continue;
- free(cstack);
-}
-
-/*
- * Create a new conditional branch.
- */
-void
-conditional_stack_push(ConditionalStack cstack, ifState new_state)
-{
- IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
-
- p->if_state = new_state;
- p->query_len = -1;
- p->paren_depth = -1;
- p->next = cstack->head;
- cstack->head = p;
-}
-
-/*
- * Destroy the topmost conditional branch.
- * Returns false if there was no branch to end.
- */
-bool
-conditional_stack_pop(ConditionalStack cstack)
-{
- IfStackElem *p = cstack->head;
-
- if (!p)
- return false;
- cstack->head = cstack->head->next;
- free(p);
- return true;
-}
-
-/*
- * Fetch the current state of the top of the stack.
- */
-ifState
-conditional_stack_peek(ConditionalStack cstack)
-{
- if (conditional_stack_empty(cstack))
- return IFSTATE_NONE;
- return cstack->head->if_state;
-}
-
-/*
- * Change the state of the topmost branch.
- * Returns false if there was no branch state to set.
- */
-bool
-conditional_stack_poke(ConditionalStack cstack, ifState new_state)
-{
- if (conditional_stack_empty(cstack))
- return false;
- cstack->head->if_state = new_state;
- return true;
-}
-
-/*
- * True if there are no active \if-blocks.
- */
-bool
-conditional_stack_empty(ConditionalStack cstack)
-{
- return cstack->head == NULL;
-}
-
-/*
- * True if we should execute commands normally; that is, the current
- * conditional branch is active, or there is no open \if block.
- */
-bool
-conditional_active(ConditionalStack cstack)
-{
- ifState s = conditional_stack_peek(cstack);
-
- return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
-}
-
-/*
- * Save current query buffer length in topmost stack entry.
- */
-void
-conditional_stack_set_query_len(ConditionalStack cstack, int len)
-{
- Assert(!conditional_stack_empty(cstack));
- cstack->head->query_len = len;
-}
-
-/*
- * Fetch last-recorded query buffer length from topmost stack entry.
- * Will return -1 if no stack or it was never saved.
- */
-int
-conditional_stack_get_query_len(ConditionalStack cstack)
-{
- if (conditional_stack_empty(cstack))
- return -1;
- return cstack->head->query_len;
-}
-
-/*
- * Save current parenthesis nesting depth in topmost stack entry.
- */
-void
-conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
-{
- Assert(!conditional_stack_empty(cstack));
- cstack->head->paren_depth = depth;
-}
-
-/*
- * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
- * Will return -1 if no stack or it was never saved.
- */
-int
-conditional_stack_get_paren_depth(ConditionalStack cstack)
-{
- if (conditional_stack_empty(cstack))
- return -1;
- return cstack->head->paren_depth;
-}
diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h
deleted file mode 100644
index 565875a..0000000
--- a/src/bin/psql/conditional.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
- *
- * src/bin/psql/conditional.h
- */
-#ifndef CONDITIONAL_H
-#define CONDITIONAL_H
-
-/*
- * Possible states of a single level of \if block.
- */
-typedef enum ifState
-{
- IFSTATE_NONE = 0, /* not currently in an \if
block */
- IFSTATE_TRUE, /* currently in an \if or \elif
that is true
- * and all
parent branches (if any) are true */
- IFSTATE_FALSE, /* currently in an \if or \elif
that is false
- * but no true
branch has yet been seen, and
- * all parent
branches (if any) are true */
- IFSTATE_IGNORED, /* currently in an \elif that
follows a true
- * branch, or
the whole \if is a child of a
- * false parent
branch */
- IFSTATE_ELSE_TRUE, /* currently in an \else that
is true and all
- * parent
branches (if any) are true */
- IFSTATE_ELSE_FALSE /* currently in an \else that
is false or
- * ignored */
-} ifState;
-
-/*
- * The state of nested \ifs is stored in a stack.
- *
- * query_len is used to determine what accumulated text to throw away at the
- * end of an inactive branch. (We could, perhaps, teach the lexer to not add
- * stuff to the query buffer in the first place when inside an inactive branch;
- * but that would be very invasive.) We also need to save and restore the
- * lexer's parenthesis nesting depth when throwing away text. (We don't need
- * to save and restore any of its other state, such as comment nesting depth,
- * because a backslash command could never appear inside a comment or SQL
- * literal.)
- */
-typedef struct IfStackElem
-{
- ifState if_state; /* current state, see enum
above */
- int query_len; /* length of query_buf
at last branch start */
- int paren_depth; /* parenthesis depth at last
branch start */
- struct IfStackElem *next; /* next surrounding \if, if any */
-} IfStackElem;
-
-typedef struct ConditionalStackData
-{
- IfStackElem *head;
-} ConditionalStackData;
-
-typedef struct ConditionalStackData *ConditionalStack;
-
-
-extern ConditionalStack conditional_stack_create(void);
-
-extern void conditional_stack_destroy(ConditionalStack cstack);
-
-extern void conditional_stack_push(ConditionalStack cstack, ifState new_state);
-
-extern bool conditional_stack_pop(ConditionalStack cstack);
-
-extern ifState conditional_stack_peek(ConditionalStack cstack);
-
-extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state);
-
-extern bool conditional_stack_empty(ConditionalStack cstack);
-
-extern bool conditional_active(ConditionalStack cstack);
-
-extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
-
-extern int conditional_stack_get_query_len(ConditionalStack cstack);
-
-extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int
depth);
-
-extern int conditional_stack_get_paren_depth(ConditionalStack cstack);
-
-#endif /* CONDITIONAL_H */
diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h
index 2354b8f..3a84565 100644
--- a/src/bin/psql/prompt.h
+++ b/src/bin/psql/prompt.h
@@ -10,7 +10,7 @@
/* enum promptStatus_t is now defined by psqlscan.h */
#include "fe_utils/psqlscan.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
char *get_prompt(promptStatus_t status, ConditionalStack cstack);
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 5e0bd0e..34df35e 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -19,7 +19,7 @@
#include "postgres_fe.h"
#include "psqlscanslash.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
#include "libpq-fe.h"
}
diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile
index 3f4ba8b..5362cff 100644
--- a/src/fe_utils/Makefile
+++ b/src/fe_utils/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
-OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o
+OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o conditional.o
all: libpgfeutils.a
diff --git a/src/fe_utils/conditional.c b/src/fe_utils/conditional.c
new file mode 100644
index 0000000..e575a9c
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,176 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/fe_utils/conditional.c
+ *
+ * A stack of automaton states to handle nested conditionals.
+ */
+#include "postgres_fe.h"
+
+#include "fe_utils/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;
+}
+
+/*
+ * Returns current stack depth, for debugging purposes.
+ */
+int
+conditional_stack_depth(ConditionalStack cstack)
+{
+ if (cstack == NULL)
+ return -1;
+ else
+ {
+ IfStackElem *p = cstack->head;
+ int depth = 0;
+ while (p != NULL)
+ {
+ depth++;
+ p = p->next;
+ }
+ return depth;
+ }
+}
+
+/*
+ * Fetch the current state of the top of the stack.
+ */
+ifState
+conditional_stack_peek(ConditionalStack cstack)
+{
+ if (conditional_stack_empty(cstack))
+ return IFSTATE_NONE;
+ return cstack->head->if_state;
+}
+
+/*
+ * Change the state of the topmost branch.
+ * Returns false if there was no branch state to set.
+ */
+bool
+conditional_stack_poke(ConditionalStack cstack, ifState new_state)
+{
+ if (conditional_stack_empty(cstack))
+ return false;
+ cstack->head->if_state = new_state;
+ return true;
+}
+
+/*
+ * True if there are no active \if-blocks.
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+ return cstack->head == NULL;
+}
+
+/*
+ * True if we should execute commands normally; that is, the current
+ * conditional branch is active, or there is no open \if block.
+ */
+bool
+conditional_active(ConditionalStack cstack)
+{
+ ifState s = conditional_stack_peek(cstack);
+
+ return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+/*
+ * Save current query buffer length in topmost stack entry.
+ */
+void
+conditional_stack_set_query_len(ConditionalStack cstack, int len)
+{
+ Assert(!conditional_stack_empty(cstack));
+ cstack->head->query_len = len;
+}
+
+/*
+ * Fetch last-recorded query buffer length from topmost stack entry.
+ * Will return -1 if no stack or it was never saved.
+ */
+int
+conditional_stack_get_query_len(ConditionalStack cstack)
+{
+ if (conditional_stack_empty(cstack))
+ return -1;
+ return cstack->head->query_len;
+}
+
+/*
+ * Save current parenthesis nesting depth in topmost stack entry.
+ */
+void
+conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
+{
+ Assert(!conditional_stack_empty(cstack));
+ cstack->head->paren_depth = depth;
+}
+
+/*
+ * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
+ * Will return -1 if no stack or it was never saved.
+ */
+int
+conditional_stack_get_paren_depth(ConditionalStack cstack)
+{
+ if (conditional_stack_empty(cstack))
+ return -1;
+ return cstack->head->paren_depth;
+}
diff --git a/src/include/fe_utils/conditional.h
b/src/include/fe_utils/conditional.h
new file mode 100644
index 0000000..20e6a46
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,98 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/include/fe_utils/conditional.h
+ *
+ * This file describes a stack of automaton states which
+ * allow a manage nested conditionals.
+ *
+ * It is used by:
+ * - "psql" interpretor for handling \if ... \endif
+ * - "pgbench" interpretor for handling \if ... \endif
+ * - "pgbench" syntax checker to test for proper nesting
+ *
+ * The stack holds the state of enclosing conditionals (are we in
+ * a true branch? in a false branch? have we already encountered
+ * a true branch?) so that the interpreter knows whether to execute
+ * code and whether to evaluate conditions.
+ */
+#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 int conditional_stack_depth(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 */