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
</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)
{
if (command->meta == META_SET)
/* backslash commands with an expression to evaluate */
PgBenchExpr *expr = command->expr;
PgBenchValue result;
if (command->meta == META_ELIF &&
conditional_stack_peek(st->cstack) == IFSTATE_TRUE)
{
PgBenchExpr *expr = command->expr;
PgBenchValue result;
/* elif after executed block, skip eval and wait for endif */
conditional_stack_poke(st->cstack, IFSTATE_IGNORED);
goto move_to_end_command;
}
if (!evaluateExpr(thread, st, expr, &result))
{
commandFailed(st, "evaluation of meta-command 'set' failed");
st->state = CSTATE_ABORTED;
break;
}
if (!evaluateExpr(thread, st, expr, &result))
{
commandFailed(st, argv[0], "evaluation of meta-command failed");
st->state = CSTATE_ABORTED;
break;
}
if (command->meta == META_SET)
{
if (!putVariableValue(st, argv[0], argv[1], &result))
{
commandFailed(st, "assignment of meta-command 'set' failed");
commandFailed(st, "set", "assignment of meta-command failed");
st->state = CSTATE_ABORTED;
break;
}
}
else if (command->meta == META_SETSHELL)
else /* if and elif evaluated cases */
{
bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2);
bool cond = valueTruth(&result);
if (timer_exceeded) /* timeout */
/* execute or not depending on evaluated condition */
if (command->meta == META_IF)
{
st->state = CSTATE_FINISHED;
break;
conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
else if (!ret) /* on error */
else /* elif */
{
commandFailed(st, "execution of meta-command 'setshell' failed");
st->state = CSTATE_ABORTED;
break;
}
else
{
/* succeeded */
/* we should get here only if the "elif" needed evaluation */
Assert(conditional_stack_peek(st->cstack) == IFSTATE_FALSE);
conditional_stack_poke(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
}
}
else if (command->meta == META_SHELL)
}
else if (command->meta == META_ELSE)
{
switch (conditional_stack_peek(st->cstack))
{
case IFSTATE_TRUE:
conditional_stack_poke(st->cstack, IFSTATE_ELSE_FALSE);
break;
case IFSTATE_FALSE: /* inconsistent if active */
case IFSTATE_IGNORED: /* inconsistent if active */
case IFSTATE_NONE: /* else without if */
case IFSTATE_ELSE_TRUE: /* else after else */
case IFSTATE_ELSE_FALSE: /* else after else */
default:
/* dead code if conditional check is ok */
Assert(false);
}
goto move_to_end_command;
}
else if (command->meta == META_ENDIF)
{
Assert(!conditional_stack_empty(st->cstack));
conditional_stack_pop(st->cstack);
goto move_to_end_command;
}
else if (command->meta == META_SETSHELL)
{
bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2);
if (timer_exceeded) /* timeout */
{
st->state = CSTATE_FINISHED;
break;
}
else if (!ret) /* on error */
{
bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
commandFailed(st, "setshell", "execution of meta-command failed");
st->state = CSTATE_ABORTED;
break;
}
else
{
/* succeeded */
}
}
else if (command->meta == META_SHELL)
{
bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
if (timer_exceeded) /* timeout */
{
st->state = CSTATE_FINISHED;
break;
}
else if (!ret) /* on error */
{
commandFailed(st, "shell", "execution of meta-command failed");
st->state = CSTATE_ABORTED;
break;
}
else
{
/* succeeded */
}
}
move_to_end_command:
/*
* executing the expression or shell command might
* take a non-negligible amount of time, so reset
* 'now'
*/
INSTR_TIME_SET_ZERO(now);
st->state = CSTATE_END_COMMAND;
}
break;
/*
* non executed conditional branch
*/
case CSTATE_SKIP_COMMAND:
Assert(!conditional_active(st->cstack));
/* quickly skip commands until something to do... */
while (true)
{
command = sql_script[st->use_file].commands[st->command];
if (timer_exceeded) /* timeout */
/* cannot reach end of script in that state */
Assert(command != NULL);
/* if this is conditional related, update conditional state */
if (command->type == META_COMMAND &&
(command->meta == META_IF ||
command->meta == META_ELIF ||
command->meta == META_ELSE ||
command->meta == META_ENDIF))
{
switch (conditional_stack_peek(st->cstack))
{
case IFSTATE_FALSE:
if (command->meta == META_IF || command->meta == META_ELIF)
{
st->state = CSTATE_FINISHED;
break;
/* we must evaluate the condition */
st->state = CSTATE_START_COMMAND;
}
else if (!ret) /* on error */
else if (command->meta == META_ELSE)
{
commandFailed(st, "execution of meta-command 'shell' failed");
st->state = CSTATE_ABORTED;
break;
/* we must execute next command */
conditional_stack_poke(st->cstack, IFSTATE_ELSE_TRUE);
st->state = CSTATE_START_COMMAND;
st->command++;
}
else
else if (command->meta == META_ENDIF)
{
/* succeeded */
Assert(!conditional_stack_empty(st->cstack));
conditional_stack_pop(st->cstack);
if (conditional_active(st->cstack))
st->state = CSTATE_START_COMMAND;
/* else state remains in CSTATE_SKIP_COMMAND */
st->command++;
}
}
break;
/*
* executing the expression or shell command might
* take a non-negligible amount of time, so reset
* 'now'
*/
INSTR_TIME_SET_ZERO(now);
case IFSTATE_IGNORED:
case IFSTATE_ELSE_FALSE:
if (command->meta == META_IF)
conditional_stack_push(st->cstack, IFSTATE_IGNORED);
else if (command->meta == META_ENDIF)
{
Assert(!conditional_stack_empty(st->cstack));
conditional_stack_pop(st->cstack);
if (conditional_active(st->cstack))
st->state = CSTATE_START_COMMAND;
}
/* could detect "else" & "elif" after "else" */
st->command++;
break;
st->state = CSTATE_END_COMMAND;
case IFSTATE_NONE:
case IFSTATE_TRUE:
case IFSTATE_ELSE_TRUE:
default:
/* inconsistent if inactive, unreachable dead code */
Assert(false);
}
}
else
{
/* skip and consider next */
st->command++;
}
if (st->state != CSTATE_SKIP_COMMAND)
break;
}
break;
......@@ -2982,7 +3135,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
fprintf(stderr, "client %d receiving\n", st->id);
if (!PQconsumeInput(st->con))
{ /* there's something wrong */
commandFailed(st, "perhaps the backend died while processing");
commandFailed(st, "SQL", "perhaps the backend died while processing");
st->state = CSTATE_ABORTED;
break;
}
......@@ -3004,7 +3157,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_END_COMMAND;
break;
default:
commandFailed(st, PQerrorMessage(st->con));
commandFailed(st, "SQL", PQerrorMessage(st->con));
PQclear(res);
st->state = CSTATE_ABORTED;
break;
......@@ -3048,9 +3201,10 @@ doCustom(TState *thread, CState *st, StatsData *agg)
INSTR_TIME_GET_DOUBLE(st->stmt_begin));
}
/* Go ahead with next command */
/* Go ahead with next command, to be executed or skipped */
st->command++;
st->state = CSTATE_START_COMMAND;
st->state = conditional_active(st->cstack) ?
CSTATE_START_COMMAND : CSTATE_SKIP_COMMAND;
break;
/*
......@@ -3061,6 +3215,13 @@ doCustom(TState *thread, CState *st, StatsData *agg)
/* transaction finished: calculate latency and do log */
processXactStats(thread, st, &now, false, agg);
/* conditional stack must be empty */
if (!conditional_stack_empty(st->cstack))
{
fprintf(stderr, "end of script reached within a conditional, missing \\endif\n");
exit(1);
}
if (is_connect)
{
finishCon(st);
......@@ -3870,19 +4031,25 @@ process_backslash_command(PsqlScanState sstate, const char *source)
/* ... and convert it to enum form */
my_command->meta = getMetaCommand(my_command->argv[0]);
if (my_command->meta == META_SET)
if (my_command->meta == META_SET ||
my_command->meta == META_IF ||
my_command->meta == META_ELIF)
{
/* For \set, collect var name, then lex the expression. */
yyscan_t yyscanner;
if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
syntax_error(source, lineno, my_command->line, my_command->argv[0],
"missing argument", NULL, -1);
/* For \set, collect var name */
if (my_command->meta == META_SET)
{
if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
syntax_error(source, lineno, my_command->line, my_command->argv[0],
"missing argument", NULL, -1);
offsets[j] = word_offset;
my_command->argv[j++] = pg_strdup(word_buf.data);
my_command->argc++;
offsets[j] = word_offset;
my_command->argv[j++] = pg_strdup(word_buf.data);
my_command->argc++;
}
/* then for all parse the expression */
yyscanner = expr_scanner_init(sstate, source, lineno, start_offset,
my_command->argv[0]);
......@@ -3978,6 +4145,12 @@ process_backslash_command(PsqlScanState sstate, const char *source)
syntax_error(source, lineno, my_command->line, my_command->argv[0],
"missing command", NULL, -1);
}
else if (my_command->meta == META_ELSE || my_command->meta == META_ENDIF)
{
if (my_command->argc != 1)
syntax_error(source, lineno, my_command->line, my_command->argv[0],
"unexpected argument", NULL, -1);
}
else
{
/* my_command->meta == META_NONE */
......@@ -3990,6 +4163,62 @@ process_backslash_command(PsqlScanState sstate, const char *source)
return my_command;
}
static void
ConditionError(const char *desc, int cmdn, const char *msg)
{
fprintf(stderr,
"condition error in script \"%s\" command %d: %s\n",
desc, cmdn, msg);
exit(1);
}
/*
* Partial evaluation of conditionals before recording and running the script.
*/
static void
CheckConditional(ParsedScript ps)
{
/* statically check conditional structure */
ConditionalStack cs = conditional_stack_create();
int i;
for (i = 0 ; ps.commands[i] != NULL ; i++)
{
Command *cmd = ps.commands[i];
if (cmd->type == META_COMMAND)
{
switch (cmd->meta)
{
case META_IF:
conditional_stack_push(cs, IFSTATE_FALSE);
break;
case META_ELIF:
if (conditional_stack_empty(cs))
ConditionError(ps.desc, i+1, "\\elif without matching \\if");
if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
ConditionError(ps.desc, i+1, "\\elif after \\else");
break;
case META_ELSE:
if (conditional_stack_empty(cs))
ConditionError(ps.desc, i+1, "\\else without matching \\if");
if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
ConditionError(ps.desc, i+1, "\\else after \\else");
conditional_stack_poke(cs, IFSTATE_ELSE_FALSE);
break;
case META_ENDIF:
if (!conditional_stack_pop(cs))
ConditionError(ps.desc, i+1, "\\endif without matching \\if");
break;
default:
/* ignore anything else... */
break;
}
}
}
if (!conditional_stack_empty(cs))
ConditionError(ps.desc, i+1, "\\if without matching \\endif");
conditional_stack_destroy(cs);
}
/*
* Parse a script (either the contents of a file, or a built-in script)
* and add it to the list of scripts.
......@@ -4275,6 +4504,8 @@ addScript(ParsedScript script)
exit(1);
}
CheckConditional(script);
sql_script[num_scripts] = script;
num_scripts++;
}
......@@ -5021,6 +5252,12 @@ main(int argc, char **argv)
}
}
/* other CState initializations */
for (i = 0; i < nclients; i++)
{
state[i].cstack = conditional_stack_create();
}
if (debug)
{
if (duration <= 0)
......
......@@ -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
......
/*
* 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.
*/
......
/*
* 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);
......
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