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:
Teodor Sigaev 2018-03-22 17:42:03 +03:00
parent b5db1d93d2
commit f67b113ac6
12 changed files with 471 additions and 86 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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,27 +2920,79 @@ doCustom(TState *thread, CState *st, StatsData *agg)
st->state = CSTATE_SLEEP;
break;
}
else
{
if (command->meta == META_SET)
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, "evaluation of meta-command 'set' failed");
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 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)
{
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 */
{
commandFailed(st, "execution of meta-command 'setshell' failed");
commandFailed(st, "setshell", "execution of meta-command failed");
st->state = CSTATE_ABORTED;
break;
}
@ -2951,7 +3024,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
}
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;
break;
}
@ -2961,6 +3034,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
}
}
move_to_end_command:
/*
* executing the expression or shell command might
* 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;
}
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,11 +4031,15 @@ 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;
/* 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);
@ -3882,7 +4047,9 @@ process_backslash_command(PsqlScanState sstate, const char *source)
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)

View file

@ -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

View file

@ -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();

View file

@ -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 \

View file

@ -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

View file

@ -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);

View file

@ -19,7 +19,7 @@
#include "postgres_fe.h"
#include "psqlscanslash.h"
#include "conditional.h"
#include "fe_utils/conditional.h"
#include "libpq-fe.h"
}

View file

@ -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

View file

@ -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.
*/

View file

@ -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);