Indeed. Here is a v2.

Here is a v3, which (1) activates better error messages from bison
and (2) improves the error reporting from the scanner as well.

 sh> ./pgbench -f bad.sql
 bad.sql:3: syntax error at column 23 in command "set"
 \set aid (1021 * :id) %
                       ^ error found here

the improved message is:
  bad.sql:3: syntax error, unexpected $end at column 23 in command "set"
  \set aid (1021 * :id) %
                        ^ error found here

Also scanner errors are better:

  sh> ./pgbench -f bad5.sql
  bad5.sql:1: unexpected character (a) at column 12 in command "set"
  \set foo 12abc
             ^ error found here

--
Fabien.
diff --git a/contrib/pgbench/exprparse.y b/contrib/pgbench/exprparse.y
index 243c6b9..0405a50 100644
--- a/contrib/pgbench/exprparse.y
+++ b/contrib/pgbench/exprparse.y
@@ -26,6 +26,10 @@ static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr,
 %expect 0
 %name-prefix="expr_yy"
 
+/* better error reporting with bison */
+%define parse.lac full
+%define parse.error verbose
+
 %union
 {
 	int64		ival;
diff --git a/contrib/pgbench/exprscan.l b/contrib/pgbench/exprscan.l
index 4c9229c..9de7dab 100644
--- a/contrib/pgbench/exprscan.l
+++ b/contrib/pgbench/exprscan.l
@@ -18,6 +18,13 @@ static YY_BUFFER_STATE scanbufhandle;
 static char *scanbuf;
 static int	scanbuflen;
 
+/* context information for error reporting */
+static char *expr_source = NULL;
+static int expr_lineno = 0;
+static char *expr_full_line = NULL;
+static char *expr_command = NULL;
+static int expr_col = 0;
+
 /* flex 2.5.4 doesn't bother with a decl for this */
 int expr_yylex(void);
 
@@ -52,7 +59,9 @@ space			[ \t\r\f]
 
 .				{
 					yycol += yyleng;
-					fprintf(stderr, "unexpected character '%s'\n", yytext);
+					syntax_error(expr_source, expr_lineno, expr_full_line, expr_command,
+								 "unexpected character", yytext, expr_col + yycol);
+					/* dead code, exit is called from syntax_error */
 					return CHAR_ERROR;
 				}
 %%
@@ -60,21 +69,27 @@ space			[ \t\r\f]
 void
 yyerror(const char *message)
 {
-	/* yyline is always 1 as pgbench calls the parser for each line...
-	 * so the interesting location information is the column number */
-	fprintf(stderr, "%s at column %d\n", message, yycol);
-	/* go on to raise the error from pgbench with more information */
-	/* exit(1); */
+	syntax_error(expr_source, expr_lineno, expr_full_line, expr_command,
+				 message, NULL, expr_col + yycol);
 }
 
 /*
  * Called before any actual parsing is done
  */
 void
-expr_scanner_init(const char *str)
+expr_scanner_init(const char *str, const char *source,
+				  const int lineno, const char *line,
+				  const char *cmd, const int ecol)
 {
 	Size	slen = strlen(str);
 
+	/* save context informations for error messages */
+	expr_source = (char *) source;
+	expr_lineno = (int) lineno;
+	expr_full_line = (char *) line;
+	expr_command = (char *) cmd;
+	expr_col = (int) ecol;
+
 	/*
 	 * Might be left over after error
 	 */
@@ -102,4 +117,9 @@ expr_scanner_finish(void)
 {
 	yy_delete_buffer(scanbufhandle);
 	pg_free(scanbuf);
+	expr_source = NULL;
+	expr_lineno = 0;
+	expr_full_line = NULL;
+	expr_command = NULL;
+	expr_col = 0;
 }
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
index 706fdf5..3e50bae 100644
--- a/contrib/pgbench/pgbench.c
+++ b/contrib/pgbench/pgbench.c
@@ -291,6 +291,7 @@ typedef struct
 	int			type;			/* command type (SQL_COMMAND or META_COMMAND) */
 	int			argc;			/* number of command words */
 	char	   *argv[MAX_ARGS]; /* command word list */
+	int			cols[MAX_ARGS]; /* corresponding column starting from 1 */
 	PgBenchExpr *expr;			/* parsed expression */
 } Command;
 
@@ -2189,6 +2190,28 @@ parseQuery(Command *cmd, const char *raw_sql)
 	return true;
 }
 
+void syntax_error(const char *source, const int lineno,
+				  const char *line, const char *command,
+				  const char *msg, const char *more, const int column)
+{
+	fprintf(stderr, "%s:%d: %s", source, lineno, msg);
+	if (more)
+		fprintf(stderr, " (%s)", more);
+	if (column != -1)
+		fprintf(stderr, " at column %d", column);
+	fprintf(stderr, " in command \"%s\"\n", command);
+	if (line) {
+		fprintf(stderr, "%s\n", line);
+		if (column != -1) {
+			int i = 0;
+			for (i = 0; i < column - 1; i++)
+				fprintf(stderr, " ");
+			fprintf(stderr, "^ error found here\n");
+		}
+	}
+	exit(1);
+}
+
 /* Parse a command; return a Command struct, or NULL if it's a comment */
 static Command *
 process_commands(char *buf, const char *source, const int lineno)
@@ -2200,6 +2223,11 @@ process_commands(char *buf, const char *source, const int lineno)
 	char	   *p,
 			   *tok;
 
+/* error message generation helper */
+#define SYNTAX_ERROR(msg, more, col)					\
+	syntax_error(source, lineno, my_commands->line,		\
+				 my_commands->argv[0], msg, more, col)
+
 	/* Make the string buf end at the next newline */
 	if ((p = strchr(buf, '\n')) != NULL)
 		*p = '\0';
@@ -2228,11 +2256,13 @@ process_commands(char *buf, const char *source, const int lineno)
 		j = 0;
 		tok = strtok(++p, delim);
 
+		/* stop "set" argument parsing the variable name */
 		if (tok != NULL && pg_strcasecmp(tok, "set") == 0)
 			max_args = 2;
 
 		while (tok != NULL)
 		{
+			my_commands->cols[j] = tok - buf + 1;
 			my_commands->argv[j++] = pg_strdup(tok);
 			my_commands->argc++;
 			if (max_args >= 0 && my_commands->argc >= max_args)
@@ -2250,9 +2280,9 @@ process_commands(char *buf, const char *source, const int lineno)
 
 			if (my_commands->argc < 4)
 			{
-				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
-				exit(1);
+				SYNTAX_ERROR("missing arguments", NULL, -1);
 			}
+
 			/* argc >= 4 */
 
 			if (my_commands->argc == 4 || /* uniform without/with "uniform" keyword */
@@ -2267,41 +2297,34 @@ process_commands(char *buf, const char *source, const int lineno)
 			{
 				if (my_commands->argc < 6)
 				{
-					fprintf(stderr, "%s(%s): missing threshold argument\n", my_commands->argv[0], my_commands->argv[4]);
-					exit(1);
+					SYNTAX_ERROR("missing threshold argument", my_commands->argv[4], -1);
 				}
 				else if (my_commands->argc > 6)
 				{
-					fprintf(stderr, "%s(%s): too many arguments (extra:",
-							my_commands->argv[0], my_commands->argv[4]);
-					for (j = 6; j < my_commands->argc; j++)
-						fprintf(stderr, " %s", my_commands->argv[j]);
-					fprintf(stderr, ")\n");
-					exit(1);
+					SYNTAX_ERROR("too many arguments", my_commands->argv[4],
+								 my_commands->cols[6]);
 				}
 			}
 			else /* cannot parse, unexpected arguments */
 			{
-				fprintf(stderr, "%s: unexpected arguments (bad:", my_commands->argv[0]);
-				for (j = 4; j < my_commands->argc; j++)
-					fprintf(stderr, " %s", my_commands->argv[j]);
-				fprintf(stderr, ")\n");
-				exit(1);
+				SYNTAX_ERROR("unexpected argument", my_commands->argv[4],
+							 my_commands->cols[4]);
 			}
 		}
 		else if (pg_strcasecmp(my_commands->argv[0], "set") == 0)
 		{
 			if (my_commands->argc < 3)
 			{
-				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
-				exit(1);
+				SYNTAX_ERROR("missing argument", NULL, -1);
 			}
 
-			expr_scanner_init(my_commands->argv[2]);
+			expr_scanner_init(my_commands->argv[2], source, lineno,
+							  my_commands->line, my_commands->argv[0],
+							  my_commands->cols[2] - 1);
 
 			if (expr_yyparse() != 0)
 			{
-				fprintf(stderr, "%s: parse error\n", my_commands->argv[0]);
+				/* dead code: exit done from syntax_error called by yyerror */
 				exit(1);
 			}
 
@@ -2313,8 +2336,7 @@ process_commands(char *buf, const char *source, const int lineno)
 		{
 			if (my_commands->argc < 2)
 			{
-				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
-				exit(1);
+				SYNTAX_ERROR("missing argument", NULL, -1);
 			}
 
 			/*
@@ -2343,12 +2365,12 @@ process_commands(char *buf, const char *source, const int lineno)
 					pg_strcasecmp(my_commands->argv[2], "ms") != 0 &&
 					pg_strcasecmp(my_commands->argv[2], "s") != 0)
 				{
-					fprintf(stderr, "%s: unknown time unit '%s' - must be us, ms or s\n",
-							my_commands->argv[0], my_commands->argv[2]);
-					exit(1);
+					SYNTAX_ERROR("unknown time unit, must be us, ms or s",
+								 my_commands->argv[2], my_commands->cols[2]);
 				}
 			}
 
+			/* this should be an error?! */
 			for (j = 3; j < my_commands->argc; j++)
 				fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
 						my_commands->argv[0], my_commands->argv[j]);
@@ -2357,22 +2379,19 @@ process_commands(char *buf, const char *source, const int lineno)
 		{
 			if (my_commands->argc < 3)
 			{
-				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
-				exit(1);
+				SYNTAX_ERROR("missing argument", NULL, -1);
 			}
 		}
 		else if (pg_strcasecmp(my_commands->argv[0], "shell") == 0)
 		{
 			if (my_commands->argc < 1)
 			{
-				fprintf(stderr, "%s: missing command\n", my_commands->argv[0]);
-				exit(1);
+				SYNTAX_ERROR("missing command", NULL, -1);
 			}
 		}
 		else
 		{
-			fprintf(stderr, "Invalid command %s\n", my_commands->argv[0]);
-			exit(1);
+			SYNTAX_ERROR("invalid command", NULL, -1);
 		}
 	}
 	else
@@ -2395,6 +2414,8 @@ process_commands(char *buf, const char *source, const int lineno)
 		}
 	}
 
+#undef SYNTAX_ERROR
+
 	return my_commands;
 }
 
diff --git a/contrib/pgbench/pgbench.h b/contrib/pgbench/pgbench.h
index 128bf11..9143a17 100644
--- a/contrib/pgbench/pgbench.h
+++ b/contrib/pgbench/pgbench.h
@@ -48,7 +48,12 @@ extern PgBenchExpr *expr_parse_result;
 extern int      expr_yyparse(void);
 extern int      expr_yylex(void);
 extern void expr_yyerror(const char *str);
-extern void expr_scanner_init(const char *str);
+extern void expr_scanner_init(const char *str, const char *source,
+							  const int lineno, const char *line,
+							  const char *cmd, const int ecol);
+extern void syntax_error(const char* source, const int lineno, const char* line,
+						 const char* cmd, const char* msg, const char* more,
+						 const int col);
 extern void expr_scanner_finish(void);
 
 extern int64 strtoint64(const char *str);
-- 
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