On Wed, Sep 24, 2014 at 2:34 PM, Fabien COELHO <coe...@cri.ensmp.fr> 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 (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers