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
This commit is contained in:
parent
b5db1d93d2
commit
f67b113ac6
12 changed files with 471 additions and 86 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "postgres_fe.h"
|
||||
|
||||
#include "psqlscanslash.h"
|
||||
#include "conditional.h"
|
||||
#include "fe_utils/conditional.h"
|
||||
|
||||
#include "libpq-fe.h"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
|
@ -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);
|
Loading…
Reference in a new issue