The home-grown lexer is missing e.g. dollar-quoting support, so this is not be parsed correctly:do $$ begin ... end; $$;
That would be very nice to handle correctly, I've used DO-blocks in pgbench scripts many times, and it's a pain to have to write them in a single line.
Attached is a version which does that (I think), and a test script. - backslash-commands can be \-continuated - sql-commands may include $$-quotes and must end with a ';' - double-dash comments and blank line are skipped.Obviously it is still a non-lexer hack which can be easily defeated, but ISTM that it handles non-contrived cases well. Anyway ISTM that dollar quoting cannot be handle as such and simply by a lexer, it is really an exception mechanism.
-- Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index 2517a3a..b816673 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -696,11 +696,16 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> </para> <para> - The format of a script file is one SQL command per line; multiline - SQL commands are not supported. Empty lines and lines beginning with - <literal>--</> are ignored. Script file lines can also be - <quote>meta commands</>, which are interpreted by <application>pgbench</> - itself, as described below. + The format of a script file is composed of lines which are each either + one SQL command or one <quote>meta command</> interpreted by + <application>pgbench</> itself, as described below. + Meta-commands can be spread over multiple lines using backslash + (<literal>\</>) continuations, in which case the set of continuated + lines is considered as just one line. + SQL commands may be spead over several lines and must be + <literal>;</>-terminated, and may contain simple dollar-quoted strings + over multiple lines. + Empty lines and lines beginning with <literal>--</> are ignored. </para> <para> @@ -768,7 +773,9 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> Examples: <programlisting> \set ntellers 10 * :scale -\set aid (1021 * :aid) % (100000 * :scale) + 1 +-- update an already defined aid: +\set aid \ + (1021 * :aid) % (100000 * :scale) + 1 </programlisting></para> </listitem> </varlistentry> @@ -931,11 +938,15 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> \setrandom tid 1 :ntellers \setrandom delta -5000 5000 BEGIN; -UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; +UPDATE pgbench_accounts + SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; -UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; -UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; -INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); +UPDATE pgbench_tellers + SET tbalance = tbalance + :delta WHERE tid = :tid; +UPDATE pgbench_branches + SET bbalance = bbalance + :delta WHERE bid = :bid; +INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) + VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); END; </programlisting> diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 95be62c..c10fb29 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -2430,7 +2430,10 @@ process_commands(char *buf, const char *source, const int lineno) } /* - * Read a line from fd, and return it in a malloc'd buffer. + * Read a possibly \-continuated (for backslash commands) or ;-terminated + * (for SQL statements) lines from fd, and return it in a malloc'd buffer. + * Also handle possible $$-quotes. + * * Return NULL at EOF. * * The buffer will typically be larger than necessary, but we don't care @@ -2443,6 +2446,13 @@ read_line_from_file(FILE *fd) char *buf; size_t buflen = BUFSIZ; size_t used = 0; + bool is_sql_statement = false; + bool is_backslash_command = false; + /* simplistic $$-quoting handling */ + int ddquote_start = 0; + int ddquote_length = 0; + int ddquote_end = 0; /* where the previous $$-quote ended */ + char *ddquote_string = NULL; buf = (char *) palloc(buflen); buf[0] = '\0'; @@ -2451,13 +2461,130 @@ read_line_from_file(FILE *fd) { size_t thislen = strlen(tmpbuf); + /* coldly skip comments and empty lines */ + { + int i = 0; + + while (i < thislen && isspace(tmpbuf[i])) + i++; + + if (tmpbuf[i] == '\0') /* blank */ + continue; + + if (tmpbuf[i] == '-' && tmpbuf[i+1] == '-') /* comment */ + continue; + } + /* Append tmpbuf to whatever we had already */ memcpy(buf + used, tmpbuf, thislen + 1); used += thislen; - /* Done if we collected a newline */ - if (thislen > 0 && tmpbuf[thislen - 1] == '\n') - break; + /* Determined what the current line is */ + if (!is_backslash_command && !is_sql_statement) + { + int i = 0; + + while (i < thislen && isspace(tmpbuf[i])) + i++; + + if (tmpbuf[i] == '\\') + is_backslash_command = true; + else if (tmpbuf[i] != '\0') + is_sql_statement = true; + } + + /* Handle simple $$-quoting, may not work if several quotes on a line. + */ + if (is_sql_statement && ddquote_string != NULL) + { + char * found = strstr(buf + ddquote_start + ddquote_length, + ddquote_string); + if (found != NULL) + { + pg_free(ddquote_string); + ddquote_string = NULL; + ddquote_end = found + ddquote_length - buf; + } + } + + /* Is there a starting $$-quote? + */ + if (is_sql_statement && ddquote_string == NULL) + { + int i = ddquote_end; + + /* \$[a-zA-Z0-9]*\$ pattern */ + while (i < thislen - 1) + { + if (buf[i] == '$') /* starting '$' */ + { + int j = i+1; + while (j < thislen && isalnum(buf[j])) + j++; /* eat alnum characters */ + if (buf[j] == '$') /* final '$' */ + { + ddquote_start = i; + ddquote_length = j-i+1; + ddquote_string = pg_malloc(ddquote_length + 1); + strncpy(ddquote_string, buf+i, ddquote_length); + ddquote_string[ddquote_length] = '\0'; + break; + } + } + i++; + } + } + + /* If we collected a newline */ + if (used > 0 && buf[used - 1] == '\n') + { + if (is_backslash_command) + { + /* Handle simple \-continuations */ + if (used >= 2 && buf[used - 2] == '\\') + { + buf[used - 2] = '\0'; + used -= 2; + } + else if (used >= 3 && buf[used - 2] == '\r' && + buf[used - 3] == '\\') + { + buf[used - 3] = '\0'; + used -= 3; + } + else + /* Else we are done */ + break; + } + else if (is_sql_statement) + { + if (ddquote_string == NULL) + { + /* look for a terminating ";" */ + int i = 2; + + /* backward skip blanks */ + while (used-i >= 0 && isspace(buf[used-i])) + i++; + + if (used-i >= 0 && buf[used-i] == ';') + break; + } + + /* scratch newline because process_commands approach to + * parsing is simplistic and expects just one line. + */ + if (buf[used-1] == '\n') + buf[used-1] = ' '; + if (used >= 2 && buf[used-2] == '\r') + { + buf[used-2] = ' '; + buf[used-1] = '\0'; + used --; + } + } + /* else it was a blank line */ + } /* Else, enlarge buf to ensure we can append next bufferload */ buflen += BUFSIZ;
test.sql
Description: application/sql
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers