diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index 3dd492c..c203c41 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -895,6 +895,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 fce7e3a..e88f782 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -2148,7 +2148,7 @@ hello 10 - + \if expression \elif expression \else diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 31ea6ca..b5ebefc 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" @@ -274,6 +275,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. * @@ -283,6 +287,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, @@ -312,6 +317,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 */ @@ -400,7 +406,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 @@ -1587,6 +1597,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) @@ -2295,6 +2306,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; @@ -2416,11 +2435,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. */ @@ -2608,6 +2627,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; /* @@ -2773,7 +2794,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 @@ -2806,7 +2827,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; } @@ -2817,77 +2838,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; - } - else if (!ret) /* on error */ - { - commandFailed(st, "execution of meta-command 'setshell' failed"); - st->state = CSTATE_ABORTED; - break; + conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE); } - else + else /* elif */ { - /* 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 */ + { + 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 */ { - bool ret = runShellCommand(st, NULL, argv + 1, argc - 1); + 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 (timer_exceeded) /* timeout */ + /* 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; @@ -2900,7 +3053,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; } @@ -2922,7 +3075,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; @@ -2966,9 +3119,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; /* @@ -2979,6 +3133,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) { PQfinish(st->con); @@ -3799,19 +3960,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]); @@ -3907,6 +4074,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 */ @@ -3919,6 +4092,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. @@ -4204,6 +4433,8 @@ addScript(ParsedScript script) exit(1); } + CheckConditional(script); + sql_script[num_scripts] = script; num_scripts++; } @@ -4950,6 +5181,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 a8b2962..1d3d134 100644 --- a/src/bin/pgbench/t/001_pgbench_with_server.pl +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -259,6 +259,12 @@ pgbench( qr{command=46.: int 46\b}, qr{command=47.: boolean true\b}, qr{command=48.: boolean true\b}, + qr{command=60.: int 60\b}, + qr{command=69.: int 69\b}, + qr{command=78.: int 78\b}, + qr{command=81.: int 81\b}, + qr{command=88.: int 88\b}, + qr{command=90.: int 0\b}, ], 'pgbench expressions', { '001_pgbench_expressions' => q{-- integer functions @@ -338,6 +344,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(60) +\elif 0 +\set nope 1 +\else +\set nope 1 +\endif +\if 1 < 0 +\set nope 1 +\elif 1 > 0 +\set ie debug(69) +\else +\set nope 1 +\endif +\if 1 < 0 +\set nope 1 +\elif 1 < 0 +\set nope 1 +\else +\set if debug(78) +\endif +\if 1 = 1 +\set ig debug(81) +\elif 0 +\set nope 1 +\endif +\if 1 = 0 +\set nope 1 +\elif 1 <> 0 +\set ih debug(88) +\endif +-- must be zero if false branches where skipped +\set nope debug(:nope) } }); =head @@ -391,7 +432,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 6ea55f8..80c5aed 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 c8eb2f9..b3166ec 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 95ad02d..29a8edd 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/conditional.c b/src/bin/psql/conditional.c deleted file mode 100644 index cebf8c7..0000000 --- a/src/bin/psql/conditional.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * psql - the PostgreSQL interactive terminal - * - * Copyright (c) 2000-2018, PostgreSQL Global Development Group - * - * src/bin/psql/conditional.c - */ -#include "postgres_fe.h" - -#include "conditional.h" - -/* - * create stack - */ -ConditionalStack -conditional_stack_create(void) -{ - ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData)); - - cstack->head = NULL; - return cstack; -} - -/* - * destroy stack - */ -void -conditional_stack_destroy(ConditionalStack cstack) -{ - while (conditional_stack_pop(cstack)) - continue; - free(cstack); -} - -/* - * Create a new conditional branch. - */ -void -conditional_stack_push(ConditionalStack cstack, ifState new_state) -{ - IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem)); - - p->if_state = new_state; - p->query_len = -1; - p->paren_depth = -1; - p->next = cstack->head; - cstack->head = p; -} - -/* - * Destroy the topmost conditional branch. - * Returns false if there was no branch to end. - */ -bool -conditional_stack_pop(ConditionalStack cstack) -{ - IfStackElem *p = cstack->head; - - if (!p) - return false; - cstack->head = cstack->head->next; - free(p); - return true; -} - -/* - * Fetch the current state of the top of the stack. - */ -ifState -conditional_stack_peek(ConditionalStack cstack) -{ - if (conditional_stack_empty(cstack)) - return IFSTATE_NONE; - return cstack->head->if_state; -} - -/* - * Change the state of the topmost branch. - * Returns false if there was no branch state to set. - */ -bool -conditional_stack_poke(ConditionalStack cstack, ifState new_state) -{ - if (conditional_stack_empty(cstack)) - return false; - cstack->head->if_state = new_state; - return true; -} - -/* - * True if there are no active \if-blocks. - */ -bool -conditional_stack_empty(ConditionalStack cstack) -{ - return cstack->head == NULL; -} - -/* - * True if we should execute commands normally; that is, the current - * conditional branch is active, or there is no open \if block. - */ -bool -conditional_active(ConditionalStack cstack) -{ - ifState s = conditional_stack_peek(cstack); - - return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE; -} - -/* - * Save current query buffer length in topmost stack entry. - */ -void -conditional_stack_set_query_len(ConditionalStack cstack, int len) -{ - Assert(!conditional_stack_empty(cstack)); - cstack->head->query_len = len; -} - -/* - * Fetch last-recorded query buffer length from topmost stack entry. - * Will return -1 if no stack or it was never saved. - */ -int -conditional_stack_get_query_len(ConditionalStack cstack) -{ - if (conditional_stack_empty(cstack)) - return -1; - return cstack->head->query_len; -} - -/* - * Save current parenthesis nesting depth in topmost stack entry. - */ -void -conditional_stack_set_paren_depth(ConditionalStack cstack, int depth) -{ - Assert(!conditional_stack_empty(cstack)); - cstack->head->paren_depth = depth; -} - -/* - * Fetch last-recorded parenthesis nesting depth from topmost stack entry. - * Will return -1 if no stack or it was never saved. - */ -int -conditional_stack_get_paren_depth(ConditionalStack cstack) -{ - if (conditional_stack_empty(cstack)) - return -1; - return cstack->head->paren_depth; -} diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h deleted file mode 100644 index 565875a..0000000 --- a/src/bin/psql/conditional.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * psql - the PostgreSQL interactive terminal - * - * Copyright (c) 2000-2018, PostgreSQL Global Development Group - * - * src/bin/psql/conditional.h - */ -#ifndef CONDITIONAL_H -#define CONDITIONAL_H - -/* - * Possible states of a single level of \if block. - */ -typedef enum ifState -{ - IFSTATE_NONE = 0, /* not currently in an \if block */ - IFSTATE_TRUE, /* currently in an \if or \elif that is true - * and all parent branches (if any) are true */ - IFSTATE_FALSE, /* currently in an \if or \elif that is false - * but no true branch has yet been seen, and - * all parent branches (if any) are true */ - IFSTATE_IGNORED, /* currently in an \elif that follows a true - * branch, or the whole \if is a child of a - * false parent branch */ - IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all - * parent branches (if any) are true */ - IFSTATE_ELSE_FALSE /* currently in an \else that is false or - * ignored */ -} ifState; - -/* - * The state of nested \ifs is stored in a stack. - * - * query_len is used to determine what accumulated text to throw away at the - * end of an inactive branch. (We could, perhaps, teach the lexer to not add - * stuff to the query buffer in the first place when inside an inactive branch; - * but that would be very invasive.) We also need to save and restore the - * lexer's parenthesis nesting depth when throwing away text. (We don't need - * to save and restore any of its other state, such as comment nesting depth, - * because a backslash command could never appear inside a comment or SQL - * literal.) - */ -typedef struct IfStackElem -{ - ifState if_state; /* current state, see enum above */ - int query_len; /* length of query_buf at last branch start */ - int paren_depth; /* parenthesis depth at last branch start */ - struct IfStackElem *next; /* next surrounding \if, if any */ -} IfStackElem; - -typedef struct ConditionalStackData -{ - IfStackElem *head; -} ConditionalStackData; - -typedef struct ConditionalStackData *ConditionalStack; - - -extern ConditionalStack conditional_stack_create(void); - -extern void conditional_stack_destroy(ConditionalStack cstack); - -extern void conditional_stack_push(ConditionalStack cstack, ifState new_state); - -extern bool conditional_stack_pop(ConditionalStack cstack); - -extern ifState conditional_stack_peek(ConditionalStack cstack); - -extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state); - -extern bool conditional_stack_empty(ConditionalStack cstack); - -extern bool conditional_active(ConditionalStack cstack); - -extern void conditional_stack_set_query_len(ConditionalStack cstack, int len); - -extern int conditional_stack_get_query_len(ConditionalStack cstack); - -extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth); - -extern int conditional_stack_get_paren_depth(ConditionalStack cstack); - -#endif /* CONDITIONAL_H */ diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h index 2354b8f..3a84565 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 5e0bd0e..34df35e 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 3f4ba8b..5362cff 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/fe_utils/conditional.c b/src/fe_utils/conditional.c new file mode 100644 index 0000000..ac6487d --- /dev/null +++ b/src/fe_utils/conditional.c @@ -0,0 +1,174 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2018, PostgreSQL Global Development Group + * + * src/bin/psql/conditional.c + */ +#include "postgres_fe.h" + +#include "fe_utils/conditional.h" + +/* + * create stack + */ +ConditionalStack +conditional_stack_create(void) +{ + ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData)); + + cstack->head = NULL; + return cstack; +} + +/* + * destroy stack + */ +void +conditional_stack_destroy(ConditionalStack cstack) +{ + while (conditional_stack_pop(cstack)) + continue; + free(cstack); +} + +/* + * Create a new conditional branch. + */ +void +conditional_stack_push(ConditionalStack cstack, ifState new_state) +{ + IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem)); + + p->if_state = new_state; + p->query_len = -1; + p->paren_depth = -1; + p->next = cstack->head; + cstack->head = p; +} + +/* + * Destroy the topmost conditional branch. + * Returns false if there was no branch to end. + */ +bool +conditional_stack_pop(ConditionalStack cstack) +{ + IfStackElem *p = cstack->head; + + if (!p) + return false; + cstack->head = cstack->head->next; + free(p); + 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. + */ +ifState +conditional_stack_peek(ConditionalStack cstack) +{ + if (conditional_stack_empty(cstack)) + return IFSTATE_NONE; + return cstack->head->if_state; +} + +/* + * Change the state of the topmost branch. + * Returns false if there was no branch state to set. + */ +bool +conditional_stack_poke(ConditionalStack cstack, ifState new_state) +{ + if (conditional_stack_empty(cstack)) + return false; + cstack->head->if_state = new_state; + return true; +} + +/* + * True if there are no active \if-blocks. + */ +bool +conditional_stack_empty(ConditionalStack cstack) +{ + return cstack->head == NULL; +} + +/* + * True if we should execute commands normally; that is, the current + * conditional branch is active, or there is no open \if block. + */ +bool +conditional_active(ConditionalStack cstack) +{ + ifState s = conditional_stack_peek(cstack); + + return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE; +} + +/* + * Save current query buffer length in topmost stack entry. + */ +void +conditional_stack_set_query_len(ConditionalStack cstack, int len) +{ + Assert(!conditional_stack_empty(cstack)); + cstack->head->query_len = len; +} + +/* + * Fetch last-recorded query buffer length from topmost stack entry. + * Will return -1 if no stack or it was never saved. + */ +int +conditional_stack_get_query_len(ConditionalStack cstack) +{ + if (conditional_stack_empty(cstack)) + return -1; + return cstack->head->query_len; +} + +/* + * Save current parenthesis nesting depth in topmost stack entry. + */ +void +conditional_stack_set_paren_depth(ConditionalStack cstack, int depth) +{ + Assert(!conditional_stack_empty(cstack)); + cstack->head->paren_depth = depth; +} + +/* + * Fetch last-recorded parenthesis nesting depth from topmost stack entry. + * Will return -1 if no stack or it was never saved. + */ +int +conditional_stack_get_paren_depth(ConditionalStack cstack) +{ + if (conditional_stack_empty(cstack)) + return -1; + return cstack->head->paren_depth; +} diff --git a/src/include/fe_utils/conditional.h b/src/include/fe_utils/conditional.h new file mode 100644 index 0000000..55206b8 --- /dev/null +++ b/src/include/fe_utils/conditional.h @@ -0,0 +1,85 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2018, PostgreSQL Global Development Group + * + * src/bin/psql/conditional.h + */ +#ifndef CONDITIONAL_H +#define CONDITIONAL_H + +/* + * Possible states of a single level of \if block. + */ +typedef enum ifState +{ + IFSTATE_NONE = 0, /* not currently in an \if block */ + IFSTATE_TRUE, /* currently in an \if or \elif that is true + * and all parent branches (if any) are true */ + IFSTATE_FALSE, /* currently in an \if or \elif that is false + * but no true branch has yet been seen, and + * all parent branches (if any) are true */ + IFSTATE_IGNORED, /* currently in an \elif that follows a true + * branch, or the whole \if is a child of a + * false parent branch */ + IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all + * parent branches (if any) are true */ + IFSTATE_ELSE_FALSE /* currently in an \else that is false or + * ignored */ +} ifState; + +/* + * The state of nested \ifs is stored in a stack. + * + * query_len is used to determine what accumulated text to throw away at the + * end of an inactive branch. (We could, perhaps, teach the lexer to not add + * stuff to the query buffer in the first place when inside an inactive branch; + * but that would be very invasive.) We also need to save and restore the + * lexer's parenthesis nesting depth when throwing away text. (We don't need + * to save and restore any of its other state, such as comment nesting depth, + * because a backslash command could never appear inside a comment or SQL + * literal.) + */ +typedef struct IfStackElem +{ + ifState if_state; /* current state, see enum above */ + int query_len; /* length of query_buf at last branch start */ + int paren_depth; /* parenthesis depth at last branch start */ + struct IfStackElem *next; /* next surrounding \if, if any */ +} IfStackElem; + +typedef struct ConditionalStackData +{ + IfStackElem *head; +} ConditionalStackData; + +typedef struct ConditionalStackData *ConditionalStack; + + +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); + +extern ifState conditional_stack_peek(ConditionalStack cstack); + +extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state); + +extern bool conditional_stack_empty(ConditionalStack cstack); + +extern bool conditional_active(ConditionalStack cstack); + +extern void conditional_stack_set_query_len(ConditionalStack cstack, int len); + +extern int conditional_stack_get_query_len(ConditionalStack cstack); + +extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth); + +extern int conditional_stack_get_paren_depth(ConditionalStack cstack); + +#endif /* CONDITIONAL_H */