diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index f07ddf1226..d52d324bf0 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -900,6 +900,21 @@ pgbench options d + + \if expression + \elif expression + \else + \endif + + + This group of commands implements nestable conditional blocks, + similarly to psql's . + Conditional expressions are identical to those with \set, + with non-zero values interpreted as true. + + + + \set varname expression diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index bfdf859731..10b97950ec 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -2169,7 +2169,7 @@ hello 10 - + \if expression \elif expression \else diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index a15aa06b19..894571e54f 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -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) diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl index 50cbb23f20..7448a96150 100644 --- a/src/bin/pgbench/t/001_pgbench_with_server.pl +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -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 diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl index 6ea55f8dae..80c5aed435 100644 --- a/src/bin/pgbench/t/002_pgbench_no_server.pl +++ b/src/bin/pgbench/t/002_pgbench_no_server.pl @@ -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(); diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile index c8eb2f95cc..b3166ecd15 100644 --- a/src/bin/psql/Makefile +++ b/src/bin/psql/Makefile @@ -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 \ diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h index 95ad02dfe2..29a8edd5a5 100644 --- a/src/bin/psql/command.h +++ b/src/bin/psql/command.h @@ -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 diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h index 2354b8f8c7..3a84565e4b 100644 --- a/src/bin/psql/prompt.h +++ b/src/bin/psql/prompt.h @@ -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); diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index 5e0bd0e774..34df35e5f4 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -19,7 +19,7 @@ #include "postgres_fe.h" #include "psqlscanslash.h" -#include "conditional.h" +#include "fe_utils/conditional.h" #include "libpq-fe.h" } diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile index 3f4ba8bf7c..5362cffd57 100644 --- a/src/fe_utils/Makefile +++ b/src/fe_utils/Makefile @@ -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 diff --git a/src/bin/psql/conditional.c b/src/fe_utils/conditional.c similarity index 83% rename from src/bin/psql/conditional.c rename to src/fe_utils/conditional.c index cebf8c766c..0af80521ce 100644 --- a/src/bin/psql/conditional.c +++ b/src/fe_utils/conditional.c @@ -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. */ diff --git a/src/bin/psql/conditional.h b/src/include/fe_utils/conditional.h similarity index 76% rename from src/bin/psql/conditional.h rename to src/include/fe_utils/conditional.h index 565875ac31..1516207197 100644 --- a/src/bin/psql/conditional.h +++ b/src/include/fe_utils/conditional.h @@ -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);