diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f07ddf1226..d52d324bf0 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -900,6 +900,21 @@ pgbench options d
+
+ \if expression
+ \elif expression
+ \else
+ \endif
+
+
+ This group of commands implements nestable conditional blocks,
+ similarly to psql's .
+ Conditional expressions are identical to those with \set,
+ with non-zero values interpreted as true.
+
+
+
+
\set varname expression
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index bfdf859731..10b97950ec 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2169,7 +2169,7 @@ hello 10
-
+
\if expression
\elif expression
\else
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index a15aa06b19..894571e54f 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;
+ conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
- else if (!ret) /* on error */
+ else /* elif */
{
- commandFailed(st, "execution of meta-command 'setshell' failed");
- st->state = CSTATE_ABORTED;
- break;
- }
- else
- {
- /* 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)
- {
- bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
-
- if (timer_exceeded) /* timeout */
- {
- st->state = CSTATE_FINISHED;
- break;
- }
- else if (!ret) /* on error */
- {
- commandFailed(st, "execution of meta-command 'shell' failed");
- st->state = CSTATE_ABORTED;
- break;
- }
- else
- {
- /* succeeded */
- }
- }
-
- /*
- * 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;
}
+ 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 */
+ {
+ 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 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)
+ {
+ /* we must evaluate the condition */
+ st->state = CSTATE_START_COMMAND;
+ }
+ else if (command->meta == META_ELSE)
+ {
+ /* we must execute next command */
+ conditional_stack_poke(st->cstack, IFSTATE_ELSE_TRUE);
+ st->state = CSTATE_START_COMMAND;
+ st->command++;
+ }
+ 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;
+ /* else state remains in CSTATE_SKIP_COMMAND */
+ st->command++;
+ }
+ break;
+
+ 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;
+
+ 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 50cbb23f20..7448a96150 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 6ea55f8dae..80c5aed435 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 c8eb2f95cc..b3166ecd15 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 95ad02dfe2..29a8edd5a5 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/prompt.h b/src/bin/psql/prompt.h
index 2354b8f8c7..3a84565e4b 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 5e0bd0e774..34df35e5f4 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 3f4ba8bf7c..5362cffd57 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/bin/psql/conditional.c b/src/fe_utils/conditional.c
similarity index 83%
rename from src/bin/psql/conditional.c
rename to src/fe_utils/conditional.c
index cebf8c766c..0af80521ce 100644
--- a/src/bin/psql/conditional.c
+++ b/src/fe_utils/conditional.c
@@ -1,13 +1,15 @@
-/*
- * psql - the PostgreSQL interactive terminal
+/*-------------------------------------------------------------------------
+ * A stack of automaton states to handle nested conditionals.
*
* Copyright (c) 2000-2018, PostgreSQL Global Development Group
*
- * src/bin/psql/conditional.c
+ * src/fe_utils/conditional.c
+ *
+ *-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
/*
* create stack
@@ -63,6 +65,27 @@ conditional_stack_pop(ConditionalStack cstack)
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.
*/
diff --git a/src/bin/psql/conditional.h b/src/include/fe_utils/conditional.h
similarity index 76%
rename from src/bin/psql/conditional.h
rename to src/include/fe_utils/conditional.h
index 565875ac31..1516207197 100644
--- a/src/bin/psql/conditional.h
+++ b/src/include/fe_utils/conditional.h
@@ -1,9 +1,24 @@
-/*
- * psql - the PostgreSQL interactive terminal
+/*-------------------------------------------------------------------------
+ * A stack of automaton states to handle nested conditionals.
+ *
+ * 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.
*
* Copyright (c) 2000-2018, PostgreSQL Global Development Group
*
- * src/bin/psql/conditional.h
+ * src/include/fe_utils/conditional.h
+ *
+ *-------------------------------------------------------------------------
*/
#ifndef CONDITIONAL_H
#define CONDITIONAL_H
@@ -60,6 +75,8 @@ 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);