Commit f67b113a authored by Teodor Sigaev's avatar Teodor Sigaev

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
parent b5db1d93
...@@ -900,6 +900,21 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d ...@@ -900,6 +900,21 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
</para> </para>
<variablelist> <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'> <varlistentry id='pgbench-metacommand-set'>
<term> <term>
<literal>\set <replaceable>varname</replaceable> <replaceable>expression</replaceable></literal> <literal>\set <replaceable>varname</replaceable> <replaceable>expression</replaceable></literal>
......
...@@ -2169,7 +2169,7 @@ hello 10 ...@@ -2169,7 +2169,7 @@ hello 10
</varlistentry> </varlistentry>
<varlistentry> <varlistentry id="psql-metacommand-if">
<term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term> <term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term>
<term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term> <term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term>
<term><literal>\else</literal></term> <term><literal>\else</literal></term>
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#endif /* ! WIN32 */ #endif /* ! WIN32 */
#include "postgres_fe.h" #include "postgres_fe.h"
#include "fe_utils/conditional.h"
#include "getopt_long.h" #include "getopt_long.h"
#include "libpq-fe.h" #include "libpq-fe.h"
...@@ -282,6 +283,9 @@ typedef enum ...@@ -282,6 +283,9 @@ typedef enum
* and we enter the CSTATE_SLEEP state to wait for it to expire. Other * and we enter the CSTATE_SLEEP state to wait for it to expire. Other
* meta-commands are executed immediately. * 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 * CSTATE_WAIT_RESULT waits until we get a result set back from the server
* for the current command. * for the current command.
* *
...@@ -291,6 +295,7 @@ typedef enum ...@@ -291,6 +295,7 @@ typedef enum
* command counter, and loops back to CSTATE_START_COMMAND state. * command counter, and loops back to CSTATE_START_COMMAND state.
*/ */
CSTATE_START_COMMAND, CSTATE_START_COMMAND,
CSTATE_SKIP_COMMAND,
CSTATE_WAIT_RESULT, CSTATE_WAIT_RESULT,
CSTATE_SLEEP, CSTATE_SLEEP,
CSTATE_END_COMMAND, CSTATE_END_COMMAND,
...@@ -320,6 +325,7 @@ typedef struct ...@@ -320,6 +325,7 @@ typedef struct
PGconn *con; /* connection handle to DB */ PGconn *con; /* connection handle to DB */
int id; /* client No. */ int id; /* client No. */
ConnectionStateEnum state; /* state machine's current state. */ ConnectionStateEnum state; /* state machine's current state. */
ConditionalStack cstack; /* enclosing conditionals state */
int use_file; /* index in sql_script for this client */ int use_file; /* index in sql_script for this client */
int command; /* command number in script */ int command; /* command number in script */
...@@ -408,7 +414,11 @@ typedef enum MetaCommand ...@@ -408,7 +414,11 @@ typedef enum MetaCommand
META_SET, /* \set */ META_SET, /* \set */
META_SETSHELL, /* \setshell */ META_SETSHELL, /* \setshell */
META_SHELL, /* \shell */ META_SHELL, /* \shell */
META_SLEEP /* \sleep */ META_SLEEP, /* \sleep */
META_IF, /* \if */
META_ELIF, /* \elif */
META_ELSE, /* \else */
META_ENDIF /* \endif */
} MetaCommand; } MetaCommand;
typedef enum QueryMode typedef enum QueryMode
...@@ -1645,6 +1655,7 @@ setBoolValue(PgBenchValue *pv, bool bval) ...@@ -1645,6 +1655,7 @@ setBoolValue(PgBenchValue *pv, bool bval)
pv->type = PGBT_BOOLEAN; pv->type = PGBT_BOOLEAN;
pv->u.bval = bval; pv->u.bval = bval;
} }
/* assign an integer value */ /* assign an integer value */
static void static void
setIntValue(PgBenchValue *pv, int64 ival) setIntValue(PgBenchValue *pv, int64 ival)
...@@ -2377,6 +2388,14 @@ getMetaCommand(const char *cmd) ...@@ -2377,6 +2388,14 @@ getMetaCommand(const char *cmd)
mc = META_SHELL; mc = META_SHELL;
else if (pg_strcasecmp(cmd, "sleep") == 0) else if (pg_strcasecmp(cmd, "sleep") == 0)
mc = META_SLEEP; 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 else
mc = META_NONE; mc = META_NONE;
return mc; return mc;
...@@ -2498,11 +2517,11 @@ preparedStatementName(char *buffer, int file, int state) ...@@ -2498,11 +2517,11 @@ preparedStatementName(char *buffer, int file, int state)
} }
static void static void
commandFailed(CState *st, const char *message) commandFailed(CState *st, const char *cmd, const char *message)
{ {
fprintf(stderr, fprintf(stderr,
"client %d aborted in command %d of script %d; %s\n", "client %d aborted in command %d (%s) of script %d; %s\n",
st->id, st->command, st->use_file, message); st->id, st->command, cmd, st->use_file, message);
} }
/* return a script number with a weighted choice. */ /* return a script number with a weighted choice. */
...@@ -2690,6 +2709,8 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2690,6 +2709,8 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_START_THROTTLE; st->state = CSTATE_START_THROTTLE;
else else
st->state = CSTATE_START_TX; st->state = CSTATE_START_TX;
/* check consistency */
Assert(conditional_stack_empty(st->cstack));
break; break;
/* /*
...@@ -2855,7 +2876,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2855,7 +2876,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
{ {
if (!sendCommand(st, command)) if (!sendCommand(st, command))
{ {
commandFailed(st, "SQL command send failed"); commandFailed(st, "SQL", "SQL command send failed");
st->state = CSTATE_ABORTED; st->state = CSTATE_ABORTED;
} }
else else
...@@ -2888,7 +2909,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2888,7 +2909,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
if (!evaluateSleep(st, argc, argv, &usec)) 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; st->state = CSTATE_ABORTED;
break; break;
} }
...@@ -2899,27 +2920,79 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2899,27 +2920,79 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_SLEEP; st->state = CSTATE_SLEEP;
break; break;
} }
else else if (command->meta == META_SET ||
{ command->meta == META_IF ||
if (command->meta == META_SET) command->meta == META_ELIF)
{ {
/* backslash commands with an expression to evaluate */
PgBenchExpr *expr = command->expr; PgBenchExpr *expr = command->expr;
PgBenchValue result; 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)) if (!evaluateExpr(thread, st, expr, &result))
{ {
commandFailed(st, "evaluation of meta-command 'set' failed"); commandFailed(st, argv[0], "evaluation of meta-command failed");
st->state = CSTATE_ABORTED; st->state = CSTATE_ABORTED;
break; break;
} }
if (command->meta == META_SET)
{
if (!putVariableValue(st, argv[0], argv[1], &result)) 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; st->state = CSTATE_ABORTED;
break; break;
} }
} }
else /* if and elif evaluated cases */
{
bool cond = valueTruth(&result);
/* execute or not depending on evaluated condition */
if (command->meta == META_IF)
{
conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
else /* elif */
{
/* 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_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) else if (command->meta == META_SETSHELL)
{ {
bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2); bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2);
...@@ -2931,7 +3004,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2931,7 +3004,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
} }
else if (!ret) /* on error */ else if (!ret) /* on error */
{ {
commandFailed(st, "execution of meta-command 'setshell' failed"); commandFailed(st, "setshell", "execution of meta-command failed");
st->state = CSTATE_ABORTED; st->state = CSTATE_ABORTED;
break; break;
} }
...@@ -2951,7 +3024,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2951,7 +3024,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
} }
else if (!ret) /* on error */ else if (!ret) /* on error */
{ {
commandFailed(st, "execution of meta-command 'shell' failed"); commandFailed(st, "shell", "execution of meta-command failed");
st->state = CSTATE_ABORTED; st->state = CSTATE_ABORTED;
break; break;
} }
...@@ -2961,6 +3034,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2961,6 +3034,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
} }
} }
move_to_end_command:
/* /*
* executing the expression or shell command might * executing the expression or shell command might
* take a non-negligible amount of time, so reset * take a non-negligible amount of time, so reset
...@@ -2970,6 +3044,85 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2970,6 +3044,85 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_END_COMMAND; 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; break;
...@@ -2982,7 +3135,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -2982,7 +3135,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
fprintf(stderr, "client %d receiving\n", st->id); fprintf(stderr, "client %d receiving\n", st->id);
if (!PQconsumeInput(st->con)) if (!PQconsumeInput(st->con))
{ /* there's something wrong */ { /* 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; st->state = CSTATE_ABORTED;
break; break;
} }
...@@ -3004,7 +3157,7 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -3004,7 +3157,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_END_COMMAND; st->state = CSTATE_END_COMMAND;
break; break;
default: default:
commandFailed(st, PQerrorMessage(st->con)); commandFailed(st, "SQL", PQerrorMessage(st->con));
PQclear(res); PQclear(res);
st->state = CSTATE_ABORTED; st->state = CSTATE_ABORTED;
break; break;
...@@ -3048,9 +3201,10 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -3048,9 +3201,10 @@ doCustom(TState *thread, CState *st, StatsData *agg)
INSTR_TIME_GET_DOUBLE(st->stmt_begin)); 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->command++;
st->state = CSTATE_START_COMMAND; st->state = conditional_active(st->cstack) ?
CSTATE_START_COMMAND : CSTATE_SKIP_COMMAND;
break; break;
/* /*
...@@ -3061,6 +3215,13 @@ doCustom(TState *thread, CState *st, StatsData *agg) ...@@ -3061,6 +3215,13 @@ doCustom(TState *thread, CState *st, StatsData *agg)
/* transaction finished: calculate latency and do log */ /* transaction finished: calculate latency and do log */
processXactStats(thread, st, &now, false, agg); 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) if (is_connect)
{ {
finishCon(st); finishCon(st);
...@@ -3870,11 +4031,15 @@ process_backslash_command(PsqlScanState sstate, const char *source) ...@@ -3870,11 +4031,15 @@ process_backslash_command(PsqlScanState sstate, const char *source)
/* ... and convert it to enum form */ /* ... and convert it to enum form */
my_command->meta = getMetaCommand(my_command->argv[0]); 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; yyscan_t yyscanner;
/* For \set, collect var name */
if (my_command->meta == META_SET)
{
if (!expr_lex_one_word(sstate, &word_buf, &word_offset)) if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
syntax_error(source, lineno, my_command->line, my_command->argv[0], syntax_error(source, lineno, my_command->line, my_command->argv[0],
"missing argument", NULL, -1); "missing argument", NULL, -1);
...@@ -3882,7 +4047,9 @@ process_backslash_command(PsqlScanState sstate, const char *source) ...@@ -3882,7 +4047,9 @@ process_backslash_command(PsqlScanState sstate, const char *source)
offsets[j] = word_offset; offsets[j] = word_offset;
my_command->argv[j++] = pg_strdup(word_buf.data); my_command->argv[j++] = pg_strdup(word_buf.data);
my_command->argc++; my_command->argc++;
}
/* then for all parse the expression */
yyscanner = expr_scanner_init(sstate, source, lineno, start_offset, yyscanner = expr_scanner_init(sstate, source, lineno, start_offset,
my_command->argv[0]); my_command->argv[0]);
...@@ -3978,6 +4145,12 @@ process_backslash_command(PsqlScanState sstate, const char *source) ...@@ -3978,6 +4145,12 @@ process_backslash_command(PsqlScanState sstate, const char *source)
syntax_error(source, lineno, my_command->line, my_command->argv[0], syntax_error(source, lineno, my_command->line, my_command->argv[0],
"missing command", NULL, -1); "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 else
{ {
/* my_command->meta == META_NONE */ /* my_command->meta == META_NONE */
...@@ -3990,6 +4163,62 @@ process_backslash_command(PsqlScanState sstate, const char *source) ...@@ -3990,6 +4163,62 @@ process_backslash_command(PsqlScanState sstate, const char *source)
return my_command; 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) * Parse a script (either the contents of a file, or a built-in script)
* and add it to the list of scripts. * and add it to the list of scripts.
...@@ -4275,6 +4504,8 @@ addScript(ParsedScript script) ...@@ -4275,6 +4504,8 @@ addScript(ParsedScript script)
exit(1); exit(1);
} }
CheckConditional(script);
sql_script[num_scripts] = script; sql_script[num_scripts] = script;
num_scripts++; num_scripts++;
} }
...@@ -5021,6 +5252,12 @@ main(int argc, char **argv) ...@@ -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 (debug)
{ {
if (duration <= 0) if (duration <= 0)
......
...@@ -264,6 +264,12 @@ pgbench( ...@@ -264,6 +264,12 @@ pgbench(
qr{command=51.: int -7793829335365542153\b}, qr{command=51.: int -7793829335365542153\b},
qr{command=52.: int -?\d+\b}, qr{command=52.: int -?\d+\b},
qr{command=53.: boolean true\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', 'pgbench expressions',
{ '001_pgbench_expressions' => q{-- integer functions { '001_pgbench_expressions' => q{-- integer functions
...@@ -349,6 +355,41 @@ pgbench( ...@@ -349,6 +355,41 @@ pgbench(
\set v2 5432 \set v2 5432
\set v3 -54.21E-2 \set v3 -54.21E-2
SELECT :v0, :v1, :v2, :v3; 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 # backslash commands
...@@ -396,7 +437,7 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i); ...@@ -396,7 +437,7 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
# SHELL # SHELL
[ 'shell bad command', 0, [ '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, [ 'shell undefined variable', 0,
[qr{undefined variable ":nosuchvariable"}], [qr{undefined variable ":nosuchvariable"}],
q{-- undefined variable in shell q{-- undefined variable in shell
......
...@@ -8,6 +8,16 @@ use warnings; ...@@ -8,6 +8,16 @@ use warnings;
use TestLib; use TestLib;
use Test::More; 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 # invoke pgbench
sub pgbench sub pgbench
{ {
...@@ -17,6 +27,28 @@ sub pgbench ...@@ -17,6 +27,28 @@ sub pgbench
$stat, $out, $err, $name); $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 # Option various errors
# #
...@@ -125,4 +157,24 @@ pgbench( ...@@ -125,4 +157,24 @@ pgbench(
qr{simple-update}, qr{select-only} ], qr{simple-update}, qr{select-only} ],
'pgbench builtin list'); '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(); done_testing();
...@@ -21,7 +21,7 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref ...@@ -21,7 +21,7 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
override LDFLAGS := -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) $(LDFLAGS) 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 \ describe.o help.o input.o large_obj.o mainloop.o \
prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \ prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
tab-complete.o variables.o \ tab-complete.o variables.o \
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
#include "fe_utils/print.h" #include "fe_utils/print.h"
#include "fe_utils/psqlscan.h" #include "fe_utils/psqlscan.h"
#include "conditional.h" #include "fe_utils/conditional.h"
typedef enum _backslashResult typedef enum _backslashResult
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
/* enum promptStatus_t is now defined by psqlscan.h */ /* enum promptStatus_t is now defined by psqlscan.h */
#include "fe_utils/psqlscan.h" #include "fe_utils/psqlscan.h"
#include "conditional.h" #include "fe_utils/conditional.h"
char *get_prompt(promptStatus_t status, ConditionalStack cstack); char *get_prompt(promptStatus_t status, ConditionalStack cstack);
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#include "postgres_fe.h" #include "postgres_fe.h"
#include "psqlscanslash.h" #include "psqlscanslash.h"
#include "conditional.h" #include "fe_utils/conditional.h"
#include "libpq-fe.h" #include "libpq-fe.h"
} }
......
...@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global ...@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS) 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 all: libpgfeutils.a
......
/* /*-------------------------------------------------------------------------
* psql - the PostgreSQL interactive terminal * A stack of automaton states to handle nested conditionals.
* *
* Copyright (c) 2000-2018, PostgreSQL Global Development Group * Copyright (c) 2000-2018, PostgreSQL Global Development Group
* *
* src/bin/psql/conditional.c * src/fe_utils/conditional.c
*
*-------------------------------------------------------------------------
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
#include "conditional.h" #include "fe_utils/conditional.h"
/* /*
* create stack * create stack
...@@ -63,6 +65,27 @@ conditional_stack_pop(ConditionalStack cstack) ...@@ -63,6 +65,27 @@ conditional_stack_pop(ConditionalStack cstack)
return true; 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. * Fetch the current state of the top of the stack.
*/ */
......
/* /*-------------------------------------------------------------------------
* 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 * Copyright (c) 2000-2018, PostgreSQL Global Development Group
* *
* src/bin/psql/conditional.h * src/include/fe_utils/conditional.h
*
*-------------------------------------------------------------------------
*/ */
#ifndef CONDITIONAL_H #ifndef CONDITIONAL_H
#define CONDITIONAL_H #define CONDITIONAL_H
...@@ -60,6 +75,8 @@ extern ConditionalStack conditional_stack_create(void); ...@@ -60,6 +75,8 @@ extern ConditionalStack conditional_stack_create(void);
extern void conditional_stack_destroy(ConditionalStack cstack); 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 void conditional_stack_push(ConditionalStack cstack, ifState new_state);
extern bool conditional_stack_pop(ConditionalStack cstack); extern bool conditional_stack_pop(ConditionalStack cstack);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment