Hello Josh,
Add backslash continuations to pgbench custom scripts. [...] IMHO this approach is the best compromise.I don't personally agree. I believe that it it worth breaking backwards compatibility to support line breaks in pgbench statements, and that if we're not going to do that, supporting \ continuations is of little value. As someone who actively uses pgbench to write custom benchmarks, I need to write queries which I can test. \ continuation does NOT work on the psql command line, so that's useless for testing my queries; I still have to reformat and troubleshoot. If we added \ continuation, I wouldn't use it. I think we should support line breaks, and require semicolons for end-of-statement. Backwards-compatability in custom pgbench scripts is not critical; pgbench scripts are neither used in produciton, nor used in automated systems much that I know of. I'm not clear on why we'd need a full SQL lexer.
Attached is a "without lexer" version which does ;-terminated SQL commands and \-continuated meta commands (may be useful for \shell and long \set expressions).
Also attached is a small pgbench script to test the feature.Without a lexer it is possible to fool pgbench with somehow contrived examples, say with:
SELECT 'hello;
world';
The ";" within the string will be considered as end-of-line.
Also, comments intermixed with sql on the same line would generate errors.
SELECT 1 -- one
+ 3;
Would fail, but comments on lines of their own are ok.
It may be argued that these are not a likely scripts and that this
behavior could be declared as a "feature" for keeping the code simple.
ISTM that it would be an overall improvement, but also the ;-termination requirement breaks backward compatibility.
-- Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index a808546..7990564 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -697,11 +697,15 @@ 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.
+ Empty lines and lines beginning with <literal>--</> are ignored.
</para>
<para>
@@ -769,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>
@@ -932,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 6f35db4..ff41d91 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -2450,7 +2450,9 @@ 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.
+ *
* Return NULL at EOF.
*
* The buffer will typically be larger than necessary, but we don't care
@@ -2463,6 +2465,8 @@ 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;
buf = (char *) palloc(buflen);
buf[0] = '\0';
@@ -2471,13 +2475,87 @@ 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;
+ if (!is_backslash_command && !is_sql_statement)
+ {
+ /* determined what the current line is */
+ 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;
+ }
+
+ /* 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)
+ {
+ /* 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;
+ else
+ {
+ /* 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 ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
