What I'm going to do now is to write a patch to remove the \cset part of the commit and post it, intending to push at some point next week.Per your request, here is a patch which removes \cset from pgbench. Sigh.
Again, only the removal is a little deeper. This lifts the constraint about not using empty queries in a compound statement, at the price of some added logic to detect the last result.
-- Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index 24833f46bc..a148138402 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -963,48 +963,6 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d </para> <variablelist> - <varlistentry id='pgbench-metacommand-cset'> - <term> - <literal>\cset [<replaceable>prefix</replaceable>]</literal> - </term> - - <listitem> - <para> - This command may be used to end SQL queries, replacing an embedded - semicolon (<literal>\;</literal>) within a compound SQL command. - </para> - - <para> - When this command is used, the preceding SQL query is expected to - return one row, the columns of which are stored into variables named after - column names, and prefixed with <replaceable>prefix</replaceable> if provided. - </para> - - <para> - The following example sends four queries as one compound SQL command, - inducing one message sent at the protocol level. - The result of the first query is stored into variable <replaceable>one</replaceable>, - the results of the third query are stored into variables <replaceable>z_three</replaceable> - and <replaceable>z_four</replaceable>, - whereas the results of the other queries are discarded. -<programlisting> --- compound of four queries -SELECT 1 AS one \cset -SELECT 2 AS two \; -SELECT 3 AS three, 4 AS four \cset z_ -SELECT 5; -</programlisting> - </para> - - <note> - <para> - <literal>\cset</literal> does not work when empty SQL queries appear - within a compound SQL command. - </para> - </note> - </listitem> - </varlistentry> - <varlistentry id='pgbench-metacommand-gset'> <term> <literal>\gset [<replaceable>prefix</replaceable>]</literal> @@ -1038,13 +996,6 @@ SELECT 1 \; SELECT 2 AS two, 3 AS three \gset p_ </programlisting> </para> - - <note> - <para> - <literal>\gset</literal> does not work when empty SQL queries appear - within a compound SQL command. - </para> - </note> </listitem> </varlistentry> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 4789ab92ee..545c4416f1 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -490,7 +490,6 @@ typedef enum MetaCommand META_SETSHELL, /* \setshell */ META_SHELL, /* \shell */ META_SLEEP, /* \sleep */ - META_CSET, /* \cset */ META_GSET, /* \gset */ META_IF, /* \if */ META_ELIF, /* \elif */ @@ -521,11 +520,9 @@ static const char *QUERYMODE[] = {"simple", "extended", "prepared"}; * argv Command arguments, the first of which is the command or SQL * string itself. For SQL commands, after post-processing * argv[0] is the same as 'lines' with variables substituted. - * nqueries In a multi-command SQL line, the number of queries. - * varprefix SQL commands terminated with \gset or \cset have this set + * varprefix SQL commands terminated with \gset have this set * to a non NULL value. If nonempty, it's used to prefix the * variable name that receives the value. - * varprefix_max Allocated size of the varprefix array. * expr Parsed expression, if needed. * stats Time spent in this command. */ @@ -537,9 +534,7 @@ typedef struct Command MetaCommand meta; int argc; char *argv[MAX_ARGS]; - int nqueries; - char **varprefix; - int varprefix_max; + char *varprefix; PgBenchExpr *expr; SimpleStats stats; } Command; @@ -619,7 +614,6 @@ static void doLog(TState *thread, CState *st, static void processXactStats(TState *thread, CState *st, instr_time *now, bool skipped, StatsData *agg); static void pgbench_error(const char *fmt,...) pg_attribute_printf(1, 2); -static void allocate_command_varprefix(Command *cmd, int totalqueries); static void addScript(ParsedScript script); static void *threadRun(void *arg); static void finishCon(CState *st); @@ -2614,8 +2608,6 @@ getMetaCommand(const char *cmd) mc = META_ELSE; else if (pg_strcasecmp(cmd, "endif") == 0) mc = META_ENDIF; - else if (pg_strcasecmp(cmd, "cset") == 0) - mc = META_CSET; else if (pg_strcasecmp(cmd, "gset") == 0) mc = META_GSET; else @@ -2848,24 +2840,30 @@ sendCommand(CState *st, Command *command) /* * Process query response from the backend. * - * If varprefix is not NULL, it's the array of variable prefix names where to - * store the results. + * If varprefix is not NULL, it's the variable name prefix where to store + * the results of the *last* command. * * Returns true if everything is A-OK, false if any error occurs. */ static bool -readCommandResponse(CState *st, char **varprefix) +readCommandResponse(CState *st, char *varprefix) { PGresult *res; int qrynum = 0; - while ((res = PQgetResult(st->con)) != NULL) + res = PQgetResult(st->con); + + while (res != NULL) { + /* look now at the next result to know whether it is the last */ + PGresult *next_res = PQgetResult(st->con); + bool is_last = (next_res == NULL); + switch (PQresultStatus(res)) { case PGRES_COMMAND_OK: /* non-SELECT commands */ case PGRES_EMPTY_QUERY: /* may be used for testing no-op overhead */ - if (varprefix && varprefix[qrynum] != NULL) + if (is_last && varprefix != NULL) { fprintf(stderr, "client %d script %d command %d query %d: expected one row, got %d\n", @@ -2876,7 +2874,7 @@ readCommandResponse(CState *st, char **varprefix) break; case PGRES_TUPLES_OK: - if (varprefix && varprefix[qrynum] != NULL) + if (is_last && varprefix != NULL) { if (PQntuples(res) != 1) { @@ -2895,9 +2893,9 @@ readCommandResponse(CState *st, char **varprefix) char *varname = PQfname(res, fld); /* allocate varname only if necessary, freed below */ - if (*varprefix[qrynum] != '\0') + if (*varprefix != '\0') varname = - psprintf("%s%s", varprefix[qrynum], varname); + psprintf("%s%s", varprefix, varname); /* store result as a string */ if (!putVariable(st, "gset", varname, @@ -2914,7 +2912,7 @@ readCommandResponse(CState *st, char **varprefix) return false; } - if (*varprefix[qrynum] != '\0') + if (*varprefix != '\0') pg_free(varname); } } @@ -2935,6 +2933,7 @@ readCommandResponse(CState *st, char **varprefix) PQclear(res); qrynum++; + res = next_res; } if (qrynum == 0) @@ -4248,7 +4247,7 @@ skip_sql_comments(char *sql_command) * struct. */ static Command * -create_sql_command(PQExpBuffer buf, const char *source, int numqueries) +create_sql_command(PQExpBuffer buf, const char *source) { Command *my_command; char *p = skip_sql_comments(buf->data); @@ -4265,9 +4264,7 @@ create_sql_command(PQExpBuffer buf, const char *source, int numqueries) my_command->meta = META_NONE; my_command->argc = 0; memset(my_command->argv, 0, sizeof(my_command->argv)); - my_command->nqueries = numqueries; my_command->varprefix = NULL; /* allocated later, if needed */ - my_command->varprefix_max = 0; my_command->expr = NULL; initSimpleStats(&my_command->stats); @@ -4284,39 +4281,14 @@ free_command(Command *command) for (int i = 0; i < command->argc; i++) pg_free(command->argv[i]); if (command->varprefix) - { - for (int i = 0; i < command->varprefix_max; i++) - if (command->varprefix[i] && - command->varprefix[i][0] != '\0') /* see ParseScript */ - pg_free(command->varprefix[i]); pg_free(command->varprefix); - } /* * It should also free expr recursively, but this is currently not needed - * as only \{g,c}set commands (which do not have an expression) are freed. + * as only gset commands (which do not have an expression) are freed. */ pg_free(command); } -/* - * append "more" text to current compound command which had been interrupted - * by \cset. - */ -static void -append_sql_command(Command *my_command, char *more, int numqueries) -{ - Assert(my_command->type == SQL_COMMAND && my_command->lines.len > 0); - - more = skip_sql_comments(more); - if (more == NULL) - return; - - /* append command text, embedding a ';' in place of the \cset */ - appendPQExpBuffer(&my_command->lines, ";\n%s", more); - - my_command->nqueries += numqueries; -} - /* * Once an SQL command is fully parsed, possibly by accumulating several * parts, complete other fields of the Command structure. @@ -4350,35 +4322,6 @@ postprocess_sql_command(Command *my_command) } } -/* - * Determine the command's varprefix size needed and allocate memory for it - */ -static void -allocate_command_varprefix(Command *cmd, int totalqueries) -{ - int new_max; - - /* sufficient space already allocated? */ - if (totalqueries <= cmd->varprefix_max) - return; - - /* determine the new array size */ - new_max = Max(cmd->varprefix_max, 2); - while (new_max < totalqueries) - new_max *= 2; - - /* enlarge the array, zero-initializing the allocated space */ - if (cmd->varprefix == NULL) - cmd->varprefix = pg_malloc0(sizeof(char *) * new_max); - else - { - cmd->varprefix = pg_realloc(cmd->varprefix, sizeof(char *) * new_max); - memset(cmd->varprefix + cmd->varprefix_max, 0, - sizeof(char *) * (new_max - cmd->varprefix_max)); - } - cmd->varprefix_max = new_max; -} - /* * Parse a backslash command; return a Command struct, or NULL if comment * @@ -4549,7 +4492,7 @@ process_backslash_command(PsqlScanState sstate, const char *source) syntax_error(source, lineno, my_command->first_line, my_command->argv[0], "unexpected argument", NULL, -1); } - else if (my_command->meta == META_CSET || my_command->meta == META_GSET) + else if (my_command->meta == META_GSET) { if (my_command->argc > 2) syntax_error(source, lineno, my_command->first_line, my_command->argv[0], @@ -4637,7 +4580,6 @@ ParseScript(const char *script, const char *desc, int weight) PQExpBufferData line_buf; int alloc_num; int index; - bool saw_cset = false; int lineno; int start_offset; @@ -4674,31 +4616,18 @@ ParseScript(const char *script, const char *desc, int weight) PsqlScanResult sr; promptStatus_t prompt; Command *command = NULL; - int semicolons; resetPQExpBuffer(&line_buf); lineno = expr_scanner_get_lineno(sstate, start_offset); sr = psql_scan(sstate, &line_buf, &prompt); - semicolons = psql_scan_get_escaped_semicolons(sstate); + /* If we collected a new SQL command, process that */ + command = create_sql_command(&line_buf, desc); - if (saw_cset) - { - /* the previous multi-line command ended with \cset */ - append_sql_command(ps.commands[index - 1], line_buf.data, - semicolons + 1); - saw_cset = false; - } - else - { - /* If we collected a new SQL command, process that */ - command = create_sql_command(&line_buf, desc, semicolons + 1); - - /* store new command */ - if (command) - ps.commands[index++] = command; - } + /* store new command */ + if (command) + ps.commands[index++] = command; /* If we reached a backslash, process that */ if (sr == PSCAN_BACKSLASH) @@ -4708,56 +4637,39 @@ ParseScript(const char *script, const char *desc, int weight) if (command) { /* - * If this is gset/cset, merge into the preceding command. (We + * If this is gset, merge into the preceding command. (We * don't use a command slot in this case). */ - if (command->meta == META_CSET || - command->meta == META_GSET) + if (command->meta == META_GSET) { - int cindex; Command *cmd; - /* - * If \cset is seen, set flag to leave the command pending - * for the next iteration to process. - */ - saw_cset = command->meta == META_CSET; - if (index == 0) syntax_error(desc, lineno, NULL, NULL, - "\\gset/cset cannot start a script", + "\\gset cannot start a script", NULL, -1); cmd = ps.commands[index - 1]; if (cmd->type != SQL_COMMAND) syntax_error(desc, lineno, NULL, NULL, - "\\gset/cset must follow a SQL command", + "\\gset must follow a SQL command", cmd->first_line, -1); - /* this {g,c}set applies to the previous query */ - cindex = cmd->nqueries - 1; - /* - * now that we know there's a {g,c}set in this command, - * allocate space for the variable name prefix array. - */ - allocate_command_varprefix(cmd, cmd->nqueries); - - /* - * Don't allow the previous command be a gset/cset; that + * Don't allow the previous command be a gset; that * would make no sense. */ - if (cmd->varprefix[cindex] != NULL) + if (cmd->varprefix != NULL) syntax_error(desc, lineno, NULL, NULL, - "\\gset/cset cannot follow one another", + "\\gset cannot follow one another", NULL, -1); /* get variable prefix */ if (command->argc <= 1 || command->argv[1][0] == '\0') - cmd->varprefix[cindex] = ""; /* avoid strdup */ + cmd->varprefix = pg_strdup(""); else - cmd->varprefix[cindex] = pg_strdup(command->argv[1]); + cmd->varprefix = pg_strdup(command->argv[1]); /* cleanup unused command */ free_command(command); diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl index 45888dc12e..e7e0042790 100644 --- a/src/bin/pgbench/t/001_pgbench_with_server.pl +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -538,22 +538,18 @@ pgbench( } }); -# working \gset and \cset +# working \gset pgbench( '-t 1', 0, - [ qr{type: .*/001_pgbench_gset_and_cset}, qr{processed: 1/1} ], + [ qr{type: .*/001_pgbench_gset}, qr{processed: 1/1} ], [ qr{command=3.: int 0\b}, qr{command=5.: int 1\b}, qr{command=6.: int 2\b}, qr{command=8.: int 3\b}, - qr{command=9.: int 4\b}, - qr{command=10.: int 5\b}, - qr{command=12.: int 6\b}, - qr{command=13.: int 7\b}, - qr{command=14.: int 8\b}, - qr{command=16.: int 9\b} ], - 'pgbench gset and cset commands', - { '001_pgbench_gset_and_cset' => q{-- test gset and cset + qr{command=10.: int 4\b}, + qr{command=12.: int 5\b} ], + 'pgbench gset command', + { '001_pgbench_gset' => q{-- test gset -- no columns SELECT \gset -- one value @@ -563,21 +559,15 @@ SELECT 0 AS i0 \gset SELECT 1 AS i1, 2 AS i2 \gset \set i debug(:i1) \set i debug(:i2) --- cset & gset to follow -SELECT :i2 + 1 AS i3, :i2 * :i2 AS i4 \cset - SELECT 5 AS i5 \gset -\set i debug(:i3) -\set i debug(:i4) -\set i debug(:i5) -- with prefix -SELECT 6 AS i6, 7 AS i7 \cset x_ - SELECT 8 AS i8 \gset y_ -\set i debug(:x_i6) -\set i debug(:x_i7) -\set i debug(:y_i8) +SELECT 3 AS i3 \gset x_ +\set i debug(:x_i3) -- overwrite existing variable -SELECT 0 AS i9, 9 AS i9 \gset -\set i debug(:i9) +SELECT 0 AS i4, 4 AS i4 \gset +\set i debug(:i4) +-- work on the last SQL command under \; +\; \; SELECT 0 AS i5 \; SELECT 5 AS i5 \; \; \gset +\set i debug(:i5) } }); # trigger many expression errors @@ -772,20 +762,17 @@ SELECT LEAST(}.join(', ', (':i') x 256).q{)} [ 'bad boolean', 2, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ], - # GSET & CSET + # GSET [ 'gset no row', 2, [qr{expected one row, got 0\b}], q{SELECT WHERE FALSE \gset} ], - [ 'cset no row', 2, - [qr{expected one row, got 0\b}], q{SELECT WHERE FALSE \cset -SELECT 1 AS i\gset}, 1 ], - [ 'gset alone', 1, [qr{gset/cset cannot start a script}], q{\gset} ], + [ 'gset alone', 1, [qr{gset cannot start a script}], q{\gset} ], [ 'gset no SQL', 1, - [qr{gset/cset must follow a SQL command}], q{\set i +1 + [qr{gset must follow a SQL command}], q{\set i +1 \gset} ], [ 'gset too many arguments', 1, [qr{too many arguments}], q{SELECT 1 \gset a b} ], [ 'gset after gset', 1, - [qr{gset/cset cannot follow one another}], q{SELECT 1 AS i \gset + [qr{gset cannot follow one another}], q{SELECT 1 AS i \gset \gset} ], [ 'gset non SELECT', 2, [qr{expected one row, got 0}], diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l index 321744cddb..b31527b94f 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -693,15 +693,8 @@ other . * substitution. We want these before {self}, also. */ -"\\"; { - /* Count semicolons in compound commands */ - cur_state->escaped_semicolons++; - /* Force a semicolon into the query buffer */ - psqlscan_emit(cur_state, yytext + 1, 1); - } - -"\\": { - /* Force a colon into the query buffer */ +"\\"[;:] { + /* Force a semi-colon or colon into the query buffer */ psqlscan_emit(cur_state, yytext + 1, 1); } @@ -1072,9 +1065,6 @@ psql_scan(PsqlScanState state, /* Set current output target */ state->output_buf = query_buf; - /* Reset number of escaped semicolons seen */ - state->escaped_semicolons = 0; - /* Set input source */ if (state->buffer_stack != NULL) yy_switch_to_buffer(state->buffer_stack->buf, state->scanner); @@ -1218,16 +1208,6 @@ psql_scan_reset(PsqlScanState state) state->dolqstart = NULL; } -/* - * Return the number of escaped semicolons in the lexed string seen by the - * previous psql_scan call. - */ -int -psql_scan_get_escaped_semicolons(PsqlScanState state) -{ - return state->escaped_semicolons; -} - /* * Reselect this lexer (psqlscan.l) after using another one. * diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h index d6fef9ff77..1cf5b2e7fa 100644 --- a/src/include/fe_utils/psqlscan.h +++ b/src/include/fe_utils/psqlscan.h @@ -90,8 +90,6 @@ extern PsqlScanResult psql_scan(PsqlScanState state, extern void psql_scan_reset(PsqlScanState state); -extern int psql_scan_get_escaped_semicolons(PsqlScanState state); - extern void psql_scan_reselect_sql_lexer(PsqlScanState state); extern bool psql_scan_in_quote(PsqlScanState state); diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h index 752cc9406a..42a738f422 100644 --- a/src/include/fe_utils/psqlscan_int.h +++ b/src/include/fe_utils/psqlscan_int.h @@ -112,7 +112,6 @@ typedef struct PsqlScanStateData int start_state; /* yylex's starting/finishing state */ int paren_depth; /* depth of nesting in parentheses */ int xcdepth; /* depth of nesting in slash-star comments */ - int escaped_semicolons; /* number of embedded (\;) semicolons */ char *dolqstart; /* current $foo$ quote start string */ /*