summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTeodor Sigaev2018-03-22 14:42:03 +0000
committerTeodor Sigaev2018-03-22 14:42:03 +0000
commitf67b113ac62777d18cd20d3c4d05be964301b936 (patch)
treebd65429918d10cf374292620ae726adba50f7617
parentb5db1d93d2a6e2d3186f8798a5d06e07b7536a1d (diff)
Add \if support to pgbench
Patch adds \if to pgbench as it done for psql. Implementation shares condition stack code with psql, so, this code is moved to fe_utils directory. Author: Fabien COELHO with minor editorization by me Review by: Vik Fearing, Fedor Sigaev Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.20.1711252200190.28523@lancre
-rw-r--r--doc/src/sgml/ref/pgbench.sgml15
-rw-r--r--doc/src/sgml/ref/psql-ref.sgml2
-rw-r--r--src/bin/pgbench/pgbench.c359
-rw-r--r--src/bin/pgbench/t/001_pgbench_with_server.pl43
-rw-r--r--src/bin/pgbench/t/002_pgbench_no_server.pl52
-rw-r--r--src/bin/psql/Makefile2
-rw-r--r--src/bin/psql/command.h2
-rw-r--r--src/bin/psql/prompt.h2
-rw-r--r--src/bin/psql/psqlscanslash.l2
-rw-r--r--src/fe_utils/Makefile2
-rw-r--r--src/fe_utils/conditional.c (renamed from src/bin/psql/conditional.c)31
-rw-r--r--src/include/fe_utils/conditional.h (renamed from src/bin/psql/conditional.h)23
12 files changed, 460 insertions, 75 deletions
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f07ddf1226e..d52d324bf0d 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 bfdf8597311..10b97950ec1 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 a15aa06b194..894571e54f2 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)
{
- if (command->meta == META_SET)
+ /* 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)
{
- PgBenchExpr *expr = command->expr;
- PgBenchValue result;
+ /* 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, "evaluation of meta-command 'set' failed");
- st->state = CSTATE_ABORTED;
- break;
- }
+ 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)
+ {
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)
+ }
+ 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 */
{
- bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
+ 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];
- if (timer_exceeded) /* timeout */
+ /* 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)
{
- 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 50cbb23f20b..7448a961501 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 6ea55f8daea..80c5aed4351 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 c8eb2f95ccf..b3166ecd158 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 95ad02dfe23..29a8edd5a5d 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 2354b8f8c7a..3a84565e4b8 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 5e0bd0e7749..34df35e5f43 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 3f4ba8bf7c9..5362cffd573 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
index cebf8c766c9..0af80521ce1 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
@@ -64,6 +66,27 @@ conditional_stack_pop(ConditionalStack cstack)
}
/*
+ * 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
diff --git a/src/bin/psql/conditional.h b/src/include/fe_utils/conditional.h
index 565875ac312..15162071976 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);