mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
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
@ -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
|
|||||||
</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 @@
|
|||||||
#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
|
|||||||
* 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
|
|||||||
* 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
|
|||||||
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
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
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)
|
|||||||
}
|
}
|
||||||
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)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
/* 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)
|
|||||||
/* ... 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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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(
|
|||||||
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(
|
|||||||
\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);
|
|||||||
|
|
||||||
# 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;
|
|||||||
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
|
|||||||
$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(
|
|||||||
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
|
|||||||
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 @@
|
|||||||
|
|
||||||
#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 @@
|
|||||||
|
|
||||||
/* 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 @@
|
|||||||
#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
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -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
|
* 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)
|
|||||||
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.
|
||||||
*/
|
*/
|
@ -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
|
* 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);
|
|||||||
|
|
||||||
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);
|
Loading…
x
Reference in New Issue
Block a user