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;

Attachment: 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

Reply via email to