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;

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