On Wed, Sep 24, 2014 at 2:34 PM, Fabien COELHO <[email protected]> wrote:
> Sigh.
>
> How to transform a trivial 10 lines patch into a probably 500+ lines project
> involving flex & bison & some non trivial data structures, and which may get
> rejected on any ground...
>
> Maybe I'll set that as a student project.
I think you're making a mountain out of a molehill. I implemented
this today in about three hours. The patch is attached. It needs
more testing, documentation, and possibly some makefile adjustments,
but it seems to basically work.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
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..67e2bf6 100644
--- a/contrib/pgbench/Makefile
+++ b/contrib/pgbench/Makefile
@@ -4,7 +4,7 @@ PGFILEDESC = "pgbench - a simple program for running benchmark tests"
PGAPPICON = win32
PROGRAM = pgbench
-OBJS = pgbench.o $(WIN32RES)
+OBJS = pgbench.o exprparse.o $(WIN32RES)
PG_CPPFLAGS = -I$(libpq_srcdir)
PG_LIBS = $(libpq_pgport) $(PTHREAD_LIBS)
@@ -18,8 +18,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..37eb538
--- /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
+
+%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..4409f08
--- /dev/null
+++ b/contrib/pgbench/exprscan.l
@@ -0,0 +1,100 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int yyline = 1; /* line number for error reporting */
+
+/* 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]
+
+%%
+
+"+" { return '+'; }
+"-" { return '-'; }
+"*" { return '*'; }
+"/" { return '/'; }
+"%" { return '%'; }
+"(" { return '('; }
+")" { return ')'; }
+:[a-zA-Z0-9_]+ { yylval.str = pg_strdup(yytext + 1); return VARIABLE; }
+[0-9]+ { yylval.ival = strtoint64(yytext); return INTEGER; }
+
+[\n] { yyline++; }
+{space} { /* ignore */ }
+
+. {
+ fprintf(stderr, "syntax error at line %d: unexpected character \"%s\"\n", yyline, yytext);
+ exit(1);
+ }
+%%
+
+void
+yyerror(const char *message)
+{
+ fprintf(stderr, "%s at line %d\n", message, yyline);
+ 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 087e0d3..dd2d0ff 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
*/
@@ -277,6 +279,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
@@ -404,7 +407,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;
@@ -860,6 +863,85 @@ getQueryParams(CState *st, const Command *command, const char **params)
params[i] = getVariable(st, command->argv[i + 1]);
}
+static bool
+evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval)
+{
+ switch (expr->etype)
+ {
+ case ENODE_INTEGER_CONSTANT:
+ {
+ *retval = expr->u.integer_constant.ival;
+ return false;
+ }
+
+ 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 true;
+ }
+ *retval = strtoint64(var);
+ return false;
+ }
+
+ case ENODE_OPERATOR:
+ {
+ int64 lval;
+ int64 rval;
+
+ if (evaluateExpr(st, expr->u.operator.lexpr, &lval))
+ return true;
+ if (evaluateExpr(st, expr->u.operator.rexpr, &rval))
+ return true;
+ switch (expr->u.operator.operator)
+ {
+ case '+':
+ *retval = lval + rval;
+ return false;
+
+ case '-':
+ *retval = lval - rval;
+ return false;
+
+ case '*':
+ *retval = lval * rval;
+ return false;
+
+ case '/':
+ if (rval == 0)
+ {
+ fprintf(stderr, "division by zero\n");
+ return true;
+ }
+ *retval = lval / rval;
+ return false;
+
+ case '%':
+ if (rval == 0)
+ {
+ fprintf(stderr, "division by zero\n");
+ return true;
+ }
+ *retval = lval % rval;
+ return false;
+ }
+
+ fprintf(stderr, "bad operator\n");
+ return true;
+ }
+
+ default:
+ break;
+ }
+
+ fprintf(stderr, "bad expression\n");
+ return true;
+}
+
/*
* Run a shell command. The result is assigned to the variable if not NULL.
* Return true if succeeded, or false on error.
@@ -1598,64 +1680,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 ((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 (evaluateExpr(st, expr, &result))
{
- 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))
{
@@ -2106,16 +2140,23 @@ process_commands(char *buf)
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)
@@ -2174,9 +2215,14 @@ process_commands(char *buf)
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)
+ expr_yyerror("bogus input");
+
+ my_commands->expr = expr_parse_result;
+
+ expr_scanner_finish();
}
else if (pg_strcasecmp(my_commands->argv[0], "sleep") == 0)
{
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
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers