Hello Alvaro,

Here is a v6 with most of your suggestions applied.

On top of evaluateExpr() we need a comment (generally I think pgbench
could do with more comments; not saying your patch should add them, just
expressing an opinion.)  Also, intuitively I would say that the return
values of that function should be reversed: return true if things are
good.

Comment & inverted return value done.

I wonder about LOCATE and LOCATION.  Can we do away with the latter, and
keep only LOCATE perhaps with a better name such as PRINT_ERROR_AT or
similar?  I would just expand an ad-hoc fprintf in the single place
where the other macro is used.

I've used just one PRINT_ERROR_AT() macro consistently.

Are we okay with only integer operands?  Is this something we would
expand in the future?  Is the gaussian/exp random stuff going to work
with integer operands, if we want to change it to use function syntax,
as expressed elsewhere?

Nothing for now, I feel it is for a later round.

[other mail] bring ERROR() macro back

I also prefer the code with it, but the cost-benefit of a pre-C99 compatible implementation seems quite low, and it does imply less (style) changes with the previous situation as it is.

--
Fabien.
diff --git a/contrib/pgbench/.gitignore b/contrib/pgbench/.gitignore
index 489a2d6..aae819e 100644
--- a/contrib/pgbench/.gitignore
+++ b/contrib/pgbench/.gitignore
@@ -1 +1,3 @@
+/exprparse.c
+/exprscan.c
 /pgbench
diff --git a/contrib/pgbench/Makefile b/contrib/pgbench/Makefile
index b8e2fc8..2d4033b 100644
--- a/contrib/pgbench/Makefile
+++ b/contrib/pgbench/Makefile
@@ -4,7 +4,9 @@ PGFILEDESC = "pgbench - a simple program for running benchmark tests"
 PGAPPICON = win32
 
 PROGRAM = pgbench
-OBJS	= pgbench.o $(WIN32RES)
+OBJS	= pgbench.o exprparse.o $(WIN32RES)
+
+EXTRA_CLEAN	= exprparse.c exprscan.c
 
 PG_CPPFLAGS = -I$(libpq_srcdir)
 PG_LIBS = $(libpq_pgport) $(PTHREAD_LIBS)
@@ -18,8 +20,22 @@ subdir = contrib/pgbench
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 include $(top_srcdir)/contrib/contrib-global.mk
+
+distprep: exprparse.c exprscan.c
 endif
 
 ifneq ($(PORTNAME), win32)
 override CFLAGS += $(PTHREAD_CFLAGS)
 endif
+
+# There is no correct way to write a rule that generates two files.
+# Rules with two targets don't have that meaning, they are merely
+# shorthand for two otherwise separate rules.  To be safe for parallel
+# make, we must chain the dependencies like this.  The semicolon is
+# important, otherwise make will choose the built-in rule for
+# gram.y=>gram.c.
+
+exprparse.h: exprparse.c ;
+
+# exprscan is compiled as part of exprparse
+exprparse.o: exprscan.c
diff --git a/contrib/pgbench/exprparse.y b/contrib/pgbench/exprparse.y
new file mode 100644
index 0000000..243c6b9
--- /dev/null
+++ b/contrib/pgbench/exprparse.y
@@ -0,0 +1,96 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * exprparse.y
+ *	  bison grammar for a simple expression syntax
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "pgbench.h"
+
+PgBenchExpr *expr_parse_result;
+
+static PgBenchExpr *make_integer_constant(int64 ival);
+static PgBenchExpr *make_variable(char *varname);
+static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr,
+		PgBenchExpr *rexpr);
+
+%}
+
+%expect 0
+%name-prefix="expr_yy"
+
+%union
+{
+	int64		ival;
+	char	   *str;
+	PgBenchExpr *expr;
+}
+
+%type <expr> expr
+%type <ival> INTEGER
+%type <str> VARIABLE
+%token INTEGER VARIABLE
+%token CHAR_ERROR /* never used, will raise a syntax error */
+
+%left	'+' '-'
+%left	'*' '/' '%'
+%right	UMINUS
+
+%%
+
+result: expr				{ expr_parse_result = $1; }
+
+expr: '(' expr ')'			{ $$ = $2; }
+	| '+' expr %prec UMINUS	{ $$ = $2; }
+	| '-' expr %prec UMINUS	{ $$ = make_op('-', make_integer_constant(0), $2); }
+	| expr '+' expr			{ $$ = make_op('+', $1, $3); }
+	| expr '-' expr			{ $$ = make_op('-', $1, $3); }
+	| expr '*' expr			{ $$ = make_op('*', $1, $3); }
+	| expr '/' expr			{ $$ = make_op('/', $1, $3); }
+	| expr '%' expr			{ $$ = make_op('%', $1, $3); }
+	| INTEGER				{ $$ = make_integer_constant($1); }
+	| VARIABLE 				{ $$ = make_variable($1); }
+	;
+
+%%
+
+static PgBenchExpr *
+make_integer_constant(int64 ival)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_INTEGER_CONSTANT;
+	expr->u.integer_constant.ival = ival;
+	return expr;
+}
+
+static PgBenchExpr *
+make_variable(char *varname)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_VARIABLE;
+	expr->u.variable.varname = varname;
+	return expr;
+}
+
+static PgBenchExpr *
+make_op(char operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_OPERATOR;
+	expr->u.operator.operator = operator;
+	expr->u.operator.lexpr = lexpr;
+	expr->u.operator.rexpr = rexpr;
+	return expr;
+}
+
+#include "exprscan.c"
diff --git a/contrib/pgbench/exprscan.l b/contrib/pgbench/exprscan.l
new file mode 100644
index 0000000..4c9229c
--- /dev/null
+++ b/contrib/pgbench/exprscan.l
@@ -0,0 +1,105 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * exprscan.l
+ *	  a lexical scanner for a simple expression syntax
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* line and column number for error reporting */
+static int	yyline = 0, yycol = 0;
+
+/* Handles to the buffer that the lexer uses internally */
+static YY_BUFFER_STATE scanbufhandle;
+static char *scanbuf;
+static int	scanbuflen;
+
+/* flex 2.5.4 doesn't bother with a decl for this */
+int expr_yylex(void);
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="expr_yy"
+
+non_newline		[^\n\r]
+space			[ \t\r\f]
+
+%%
+
+"+"				{ yycol += yyleng; return '+'; }
+"-"				{ yycol += yyleng; return '-'; }
+"*"				{ yycol += yyleng; return '*'; }
+"/"				{ yycol += yyleng; return '/'; }
+"%"				{ yycol += yyleng; return '%'; }
+"("				{ yycol += yyleng; return '('; }
+")"				{ yycol += yyleng; return ')'; }
+:[a-zA-Z0-9_]+	{ yycol += yyleng; yylval.str = pg_strdup(yytext + 1); return VARIABLE; }
+[0-9]+			{ yycol += yyleng; yylval.ival = strtoint64(yytext); return INTEGER; }
+
+[\n]			{ yycol = 0; yyline++; }
+{space}			{ yycol += yyleng; /* ignore */ }
+
+.				{
+					yycol += yyleng;
+					fprintf(stderr, "unexpected character '%s'\n", yytext);
+					return CHAR_ERROR;
+				}
+%%
+
+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); */
+}
+
+/*
+ * Called before any actual parsing is done
+ */
+void
+expr_scanner_init(const char *str)
+{
+	Size	slen = strlen(str);
+
+	/*
+	 * Might be left over after error
+	 */
+	if (YY_CURRENT_BUFFER)
+		yy_delete_buffer(YY_CURRENT_BUFFER);
+
+	/*
+	 * Make a scan buffer with special termination needed by flex.
+	 */
+	scanbuflen = slen;
+	scanbuf = pg_malloc(slen + 2);
+	memcpy(scanbuf, str, slen);
+	scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
+	scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
+
+	BEGIN(INITIAL);
+}
+
+
+/*
+ * Called after parsing is done to clean up after seg_scanner_init()
+ */
+void
+expr_scanner_finish(void)
+{
+	yy_delete_buffer(scanbufhandle);
+	pg_free(scanbuf);
+}
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
index 2761d1d6..018f834 100644
--- a/contrib/pgbench/pgbench.c
+++ b/contrib/pgbench/pgbench.c
@@ -57,6 +57,8 @@
 #define M_PI 3.14159265358979323846
 #endif
 
+#include "pgbench.h"
+
 /*
  * Multi-platform pthread implementations
  */
@@ -289,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 */
+	PgBenchExpr *expr;			/* parsed expression */
 } Command;
 
 typedef struct
@@ -423,7 +426,7 @@ usage(void)
  * This function is a modified version of scanint8() from
  * src/backend/utils/adt/int8.c.
  */
-static int64
+int64
 strtoint64(const char *str)
 {
 	const char *ptr = str;
@@ -880,6 +883,91 @@ getQueryParams(CState *st, const Command *command, const char **params)
 }
 
 /*
+ * Recursive evaluation of an expression in a pgbench script
+ * using the current state of variables.
+ * Returns whether the evaluation was ok,
+ * the value itself is returned through the retval pointer.
+ */
+static bool
+evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval)
+{
+	switch (expr->etype)
+	{
+		case ENODE_INTEGER_CONSTANT:
+			{
+				*retval = expr->u.integer_constant.ival;
+				return true;
+			}
+
+		case ENODE_VARIABLE:
+			{
+				char	   *var;
+
+				if ((var = getVariable(st, expr->u.variable.varname)) == NULL)
+				{
+					fprintf(stderr, "undefined variable %s\n",
+						expr->u.variable.varname);
+					return false;
+				}
+				*retval = strtoint64(var);
+				return true;
+			}
+
+		case ENODE_OPERATOR:
+			{
+				int64	lval;
+				int64	rval;
+
+				if (!evaluateExpr(st, expr->u.operator.lexpr, &lval))
+					return false;
+				if (!evaluateExpr(st, expr->u.operator.rexpr, &rval))
+					return false;
+				switch (expr->u.operator.operator)
+				{
+					case '+':
+						*retval = lval + rval;
+						return true;
+
+					case '-':
+						*retval = lval - rval;
+						return true;
+
+					case '*':
+						*retval = lval * rval;
+						return true;
+
+					case '/':
+						if (rval == 0)
+						{
+							fprintf(stderr, "division by zero\n");
+							return false;
+						}
+						*retval = lval / rval;
+						return true;
+
+					case '%':
+						if (rval == 0)
+						{
+							fprintf(stderr, "division by zero\n");
+							return false;
+						}
+						*retval = lval % rval;
+						return true;
+				}
+
+				fprintf(stderr, "bad operator\n");
+				return false;
+			}
+
+		default:
+			break;
+	}
+
+	fprintf(stderr, "bad expression\n");
+	return false;
+}
+
+/*
  * Run a shell command. The result is assigned to the variable if not NULL.
  * Return true if succeeded, or false on error.
  */
@@ -1515,64 +1603,16 @@ top:
 		}
 		else if (pg_strcasecmp(argv[0], "set") == 0)
 		{
-			char	   *var;
-			int64		ope1,
-						ope2;
 			char		res[64];
+			PgBenchExpr *expr = commands[st->state]->expr;
+			int64		result;
 
-			if (*argv[2] == ':')
+			if (!evaluateExpr(st, expr, &result))
 			{
-				if ((var = getVariable(st, argv[2] + 1)) == NULL)
-				{
-					fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[2]);
-					st->ecnt++;
-					return true;
-				}
-				ope1 = strtoint64(var);
-			}
-			else
-				ope1 = strtoint64(argv[2]);
-
-			if (argc < 5)
-				snprintf(res, sizeof(res), INT64_FORMAT, ope1);
-			else
-			{
-				if (*argv[4] == ':')
-				{
-					if ((var = getVariable(st, argv[4] + 1)) == NULL)
-					{
-						fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[4]);
-						st->ecnt++;
-						return true;
-					}
-					ope2 = strtoint64(var);
-				}
-				else
-					ope2 = strtoint64(argv[4]);
-
-				if (strcmp(argv[3], "+") == 0)
-					snprintf(res, sizeof(res), INT64_FORMAT, ope1 + ope2);
-				else if (strcmp(argv[3], "-") == 0)
-					snprintf(res, sizeof(res), INT64_FORMAT, ope1 - ope2);
-				else if (strcmp(argv[3], "*") == 0)
-					snprintf(res, sizeof(res), INT64_FORMAT, ope1 * ope2);
-				else if (strcmp(argv[3], "/") == 0)
-				{
-					if (ope2 == 0)
-					{
-						fprintf(stderr, "%s: division by zero\n", argv[0]);
-						st->ecnt++;
-						return true;
-					}
-					snprintf(res, sizeof(res), INT64_FORMAT, ope1 / ope2);
-				}
-				else
-				{
-					fprintf(stderr, "%s: unsupported operator %s\n", argv[0], argv[3]);
-					st->ecnt++;
-					return true;
-				}
+				st->ecnt++;
+				return true;
 			}
+			sprintf(res, INT64_FORMAT, result);
 
 			if (!putVariable(st, argv[0], argv[1], res))
 			{
@@ -2151,7 +2191,7 @@ parseQuery(Command *cmd, const char *raw_sql)
 
 /* Parse a command; return a Command struct, or NULL if it's a comment */
 static Command *
-process_commands(char *buf)
+process_commands(char *buf, const char * source, const int lineno)
 {
 	const char	delim[] = " \f\n\r\t\v";
 
@@ -2180,18 +2220,29 @@ process_commands(char *buf)
 	my_commands->type = 0;		/* until set */
 	my_commands->argc = 0;
 
+#define PRINT_ERROR_AT(current_line)									\
+	fprintf(stderr, "error while processing \"%s\" line %d: %s\n",		\
+			source, lineno, current_line)
+
 	if (*p == '\\')
 	{
+		int		max_args = -1;
 		my_commands->type = META_COMMAND;
 
 		j = 0;
 		tok = strtok(++p, delim);
 
+		if (tok != NULL && pg_strcasecmp(tok, "set") == 0)
+			max_args = 2;
+
 		while (tok != NULL)
 		{
 			my_commands->argv[j++] = pg_strdup(tok);
 			my_commands->argc++;
-			tok = strtok(NULL, delim);
+			if (max_args >= 0 && my_commands->argc >= max_args)
+				tok = strtok(NULL, "");
+			else
+				tok = strtok(NULL, delim);
 		}
 
 		if (pg_strcasecmp(my_commands->argv[0], "setrandom") == 0)
@@ -2203,12 +2254,14 @@ process_commands(char *buf)
 
 			if (my_commands->argc < 4)
 			{
+				PRINT_ERROR_AT(my_commands->line);
 				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
 				exit(1);
 			}
 			/* argc >= 4 */
 
-			if (my_commands->argc == 4 || /* uniform without/with "uniform" keyword */
+			/* uniform without/with "uniform" keyword */
+			if (my_commands->argc == 4 ||
 				(my_commands->argc == 5 &&
 				 pg_strcasecmp(my_commands->argv[4], "uniform") == 0))
 			{
@@ -2220,11 +2273,14 @@ process_commands(char *buf)
 			{
 				if (my_commands->argc < 6)
 				{
-					fprintf(stderr, "%s(%s): missing threshold argument\n", my_commands->argv[0], my_commands->argv[4]);
+					PRINT_ERROR_AT(my_commands->line);
+					fprintf(stderr, "%s(%s): missing threshold argument\n",
+							my_commands->argv[0], my_commands->argv[4]);
 					exit(1);
 				}
 				else if (my_commands->argc > 6)
 				{
+					PRINT_ERROR_AT(my_commands->line);
 					fprintf(stderr, "%s(%s): too many arguments (extra:",
 							my_commands->argv[0], my_commands->argv[4]);
 					for (j = 6; j < my_commands->argc; j++)
@@ -2235,7 +2291,9 @@ process_commands(char *buf)
 			}
 			else /* cannot parse, unexpected arguments */
 			{
-				fprintf(stderr, "%s: unexpected arguments (bad:", my_commands->argv[0]);
+				PRINT_ERROR_AT(my_commands->line);
+				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");
@@ -2246,18 +2304,28 @@ process_commands(char *buf)
 		{
 			if (my_commands->argc < 3)
 			{
+				PRINT_ERROR_AT(my_commands->line);
 				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
 				exit(1);
 			}
 
-			for (j = my_commands->argc < 5 ? 3 : 5; j < my_commands->argc; j++)
-				fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
-						my_commands->argv[0], my_commands->argv[j]);
+			expr_scanner_init(my_commands->argv[2]);
+
+			if (expr_yyparse() != 0) {
+				PRINT_ERROR_AT(my_commands->argv[2]);
+				fprintf(stderr, "bogus input\n");
+				exit(1);
+			}
+
+			my_commands->expr = expr_parse_result;
+
+			expr_scanner_finish();
 		}
 		else if (pg_strcasecmp(my_commands->argv[0], "sleep") == 0)
 		{
 			if (my_commands->argc < 2)
 			{
+				PRINT_ERROR_AT(my_commands->line);
 				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
 				exit(1);
 			}
@@ -2288,12 +2356,15 @@ process_commands(char *buf)
 					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",
+					PRINT_ERROR_AT(my_commands->line);
+					fprintf(stderr,
+							"%s: unknown time unit '%s' - must be us, ms or s\n",
 							my_commands->argv[0], my_commands->argv[2]);
 					exit(1);
 				}
 			}
 
+			/* should really report an error and stop... */
 			for (j = 3; j < my_commands->argc; j++)
 				fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
 						my_commands->argv[0], my_commands->argv[j]);
@@ -2302,6 +2373,7 @@ process_commands(char *buf)
 		{
 			if (my_commands->argc < 3)
 			{
+				PRINT_ERROR_AT(my_commands->line);
 				fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
 				exit(1);
 			}
@@ -2310,12 +2382,14 @@ process_commands(char *buf)
 		{
 			if (my_commands->argc < 1)
 			{
+				PRINT_ERROR_AT(my_commands->line);
 				fprintf(stderr, "%s: missing command\n", my_commands->argv[0]);
 				exit(1);
 			}
 		}
 		else
 		{
+			PRINT_ERROR_AT(my_commands->line);
 			fprintf(stderr, "Invalid command %s\n", my_commands->argv[0]);
 			exit(1);
 		}
@@ -2341,6 +2415,7 @@ process_commands(char *buf)
 	}
 
 	return my_commands;
+#undef PRINT_ERROR_AT
 }
 
 /*
@@ -2393,7 +2468,7 @@ process_file(char *filename)
 
 	Command   **my_commands;
 	FILE	   *fd;
-	int			lineno;
+	int			lineno, index;
 	char	   *buf;
 	int			alloc_num;
 
@@ -2416,22 +2491,24 @@ process_file(char *filename)
 	}
 
 	lineno = 0;
+	index = 0;
 
 	while ((buf = read_line_from_file(fd)) != NULL)
 	{
 		Command    *command;
+		lineno += 1;
 
-		command = process_commands(buf);
+		command = process_commands(buf, filename, lineno);
 
 		free(buf);
 
 		if (command == NULL)
 			continue;
 
-		my_commands[lineno] = command;
-		lineno++;
+		my_commands[index] = command;
+		index++;
 
-		if (lineno >= alloc_num)
+		if (index >= alloc_num)
 		{
 			alloc_num += COMMANDS_ALLOC_NUM;
 			my_commands = pg_realloc(my_commands, sizeof(Command *) * alloc_num);
@@ -2439,7 +2516,7 @@ process_file(char *filename)
 	}
 	fclose(fd);
 
-	my_commands[lineno] = NULL;
+	my_commands[index] = NULL;
 
 	sql_files[num_files++] = my_commands;
 
@@ -2447,12 +2524,12 @@ process_file(char *filename)
 }
 
 static Command **
-process_builtin(char *tb)
+process_builtin(char *tb, const char * source)
 {
 #define COMMANDS_ALLOC_NUM 128
 
 	Command   **my_commands;
-	int			lineno;
+	int			lineno, index;
 	char		buf[BUFSIZ];
 	int			alloc_num;
 
@@ -2460,6 +2537,7 @@ process_builtin(char *tb)
 	my_commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
 
 	lineno = 0;
+	index = 0;
 
 	for (;;)
 	{
@@ -2478,21 +2556,23 @@ process_builtin(char *tb)
 
 		*p = '\0';
 
-		command = process_commands(buf);
+		lineno += 1;
+
+		command = process_commands(buf, source, lineno);
 		if (command == NULL)
 			continue;
 
-		my_commands[lineno] = command;
-		lineno++;
+		my_commands[index] = command;
+		index++;
 
-		if (lineno >= alloc_num)
+		if (index >= alloc_num)
 		{
 			alloc_num += COMMANDS_ALLOC_NUM;
 			my_commands = pg_realloc(my_commands, sizeof(Command *) * alloc_num);
 		}
 	}
 
-	my_commands[lineno] = NULL;
+	my_commands[index] = NULL;
 
 	return my_commands;
 }
@@ -3222,17 +3302,20 @@ main(int argc, char **argv)
 	switch (ttype)
 	{
 		case 0:
-			sql_files[0] = process_builtin(tpc_b);
+			sql_files[0] = process_builtin(tpc_b,
+										   "<builtin: TPC-B (sort of)>");
 			num_files = 1;
 			break;
 
 		case 1:
-			sql_files[0] = process_builtin(select_only);
+			sql_files[0] = process_builtin(select_only,
+										   "<builtin: select only>");
 			num_files = 1;
 			break;
 
 		case 2:
-			sql_files[0] = process_builtin(simple_update);
+			sql_files[0] = process_builtin(simple_update,
+										   "<builtin: simple update>");
 			num_files = 1;
 			break;
 
diff --git a/contrib/pgbench/pgbench.h b/contrib/pgbench/pgbench.h
new file mode 100644
index 0000000..128bf11
--- /dev/null
+++ b/contrib/pgbench/pgbench.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgbench.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PGBENCH_H
+#define PGBENCH_H
+
+typedef enum PgBenchExprType
+{
+	ENODE_INTEGER_CONSTANT,
+	ENODE_VARIABLE,
+	ENODE_OPERATOR
+} PgBenchExprType;
+
+struct PgBenchExpr;
+typedef struct PgBenchExpr PgBenchExpr;
+
+struct PgBenchExpr
+{
+	PgBenchExprType	etype;
+	union
+	{
+		struct
+		{
+			int64 ival;
+		} integer_constant;
+		struct
+		{
+			char *varname;
+		} variable;
+		struct
+		{
+			char operator;
+			PgBenchExpr	*lexpr;
+			PgBenchExpr *rexpr;
+		} operator;
+	} u;
+};
+
+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_finish(void);
+
+extern int64 strtoint64(const char *str);
+
+#endif
diff --git a/doc/src/sgml/pgbench.sgml b/doc/src/sgml/pgbench.sgml
index 7d203cd..d3b9043 100644
--- a/doc/src/sgml/pgbench.sgml
+++ b/doc/src/sgml/pgbench.sgml
@@ -751,22 +751,25 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   <variablelist>
    <varlistentry>
     <term>
-     <literal>\set <replaceable>varname</> <replaceable>operand1</> [ <replaceable>operator</> <replaceable>operand2</> ]</literal>
+     <literal>\set <replaceable>varname</> <replaceable>expression</>
     </term>
 
     <listitem>
      <para>
-      Sets variable <replaceable>varname</> to a calculated integer value.
-      Each <replaceable>operand</> is either an integer constant or a
-      <literal>:</><replaceable>variablename</> reference to a variable
-      having an integer value.  The <replaceable>operator</> can be
-      <literal>+</>, <literal>-</>, <literal>*</>, or <literal>/</>.
+      Sets variable <replaceable>varname</> to an integer value calculated
+      from <replaceable>expression</>.
+      The expression may contain integer constants such as <literal>5432</>,
+      references to variables <literal>:</><replaceable>variablename</>,
+      and expressions composed of unary (<literal>-</>) or  binary operators
+      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>, <literal>%</>)
+      with their usual associativity and possibly parentheses.
      </para>
 
      <para>
-      Example:
+      Examples:
 <programlisting>
 \set ntellers 10 * :scale
+\set aid (1021 * :aid) % (100000 * :scale) + 1
 </programlisting></para>
     </listitem>
    </varlistentry>
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4336f2e..463ab33 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -49,6 +49,7 @@ my $contrib_extraincludes =
   { 'tsearch2' => ['contrib/tsearch2'], 'dblink' => ['src/backend'] };
 my $contrib_extrasource = {
 	'cube' => [ 'cubescan.l', 'cubeparse.y' ],
+	'pgbench' => [ 'exprscan.l', 'exprparse.y' ],
 	'seg'  => [ 'segscan.l',  'segparse.y' ], };
 my @contrib_excludes = ('pgcrypto', 'intagg', 'sepgsql');
 
-- 
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