Skip to content

Commit 878fdcb

Browse files
committed
pgbench: Add a real expression syntax to \set
Previously, you could do \set variable operand1 operator operand2, but nothing more complicated. Now, you can \set variable expression, which makes it much simpler to do multi-step calculations here. This also adds support for the modulo operator (%), with the same semantics as in C. Robert Haas and Fabien Coelho, reviewed by Álvaro Herrera and Stephen Frost
1 parent ebd092b commit 878fdcb

File tree

8 files changed

+425
-84
lines changed

8 files changed

+425
-84
lines changed

contrib/pgbench/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
/exprparse.c
2+
/exprscan.c
13
/pgbench

contrib/pgbench/Makefile

+16-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ PGFILEDESC = "pgbench - a simple program for running benchmark tests"
44
PGAPPICON = win32
55

66
PROGRAM = pgbench
7-
OBJS = pgbench.o $(WIN32RES)
7+
OBJS = pgbench.o exprparse.o $(WIN32RES)
8+
9+
EXTRA_CLEAN = exprparse.c exprscan.c
810

911
PG_CPPFLAGS = -I$(libpq_srcdir)
1012
PG_LIBS = $(libpq_pgport) $(PTHREAD_LIBS)
@@ -18,8 +20,21 @@ subdir = contrib/pgbench
1820
top_builddir = ../..
1921
include $(top_builddir)/src/Makefile.global
2022
include $(top_srcdir)/contrib/contrib-global.mk
23+
24+
distprep: exprparse.c exprscan.c
2125
endif
2226

2327
ifneq ($(PORTNAME), win32)
2428
override CFLAGS += $(PTHREAD_CFLAGS)
2529
endif
30+
31+
# There is no correct way to write a rule that generates two files.
32+
# Rules with two targets don't have that meaning, they are merely
33+
# shorthand for two otherwise separate rules. To be safe for parallel
34+
# make, we must chain the dependencies like this. The semicolon is
35+
# important; otherwise, make will choose the built-in rule.
36+
37+
exprparse.h: exprparse.c ;
38+
39+
# exprscan is compiled as part of exprparse
40+
exprparse.o: exprscan.c

contrib/pgbench/exprparse.y

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
%{
2+
/*-------------------------------------------------------------------------
3+
*
4+
* exprparse.y
5+
* bison grammar for a simple expression syntax
6+
*
7+
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
8+
* Portions Copyright (c) 1994, Regents of the University of California
9+
*
10+
*-------------------------------------------------------------------------
11+
*/
12+
13+
#include "postgres_fe.h"
14+
15+
#include "pgbench.h"
16+
17+
PgBenchExpr *expr_parse_result;
18+
19+
static PgBenchExpr *make_integer_constant(int64 ival);
20+
static PgBenchExpr *make_variable(char *varname);
21+
static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr,
22+
PgBenchExpr *rexpr);
23+
24+
%}
25+
26+
%expect 0
27+
%name-prefix="expr_yy"
28+
29+
%union
30+
{
31+
int64 ival;
32+
char *str;
33+
PgBenchExpr *expr;
34+
}
35+
36+
%type <expr> expr
37+
%type <ival> INTEGER
38+
%type <str> VARIABLE
39+
%token INTEGER VARIABLE
40+
%token CHAR_ERROR /* never used, will raise a syntax error */
41+
42+
%left '+' '-'
43+
%left '*' '/' '%'
44+
%right UMINUS
45+
46+
%%
47+
48+
result: expr { expr_parse_result = $1; }
49+
50+
expr: '(' expr ')' { $$ = $2; }
51+
| '+' expr %prec UMINUS { $$ = $2; }
52+
| '-' expr %prec UMINUS { $$ = make_op('-', make_integer_constant(0), $2); }
53+
| expr '+' expr { $$ = make_op('+', $1, $3); }
54+
| expr '-' expr { $$ = make_op('-', $1, $3); }
55+
| expr '*' expr { $$ = make_op('*', $1, $3); }
56+
| expr '/' expr { $$ = make_op('/', $1, $3); }
57+
| expr '%' expr { $$ = make_op('%', $1, $3); }
58+
| INTEGER { $$ = make_integer_constant($1); }
59+
| VARIABLE { $$ = make_variable($1); }
60+
;
61+
62+
%%
63+
64+
static PgBenchExpr *
65+
make_integer_constant(int64 ival)
66+
{
67+
PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
68+
69+
expr->etype = ENODE_INTEGER_CONSTANT;
70+
expr->u.integer_constant.ival = ival;
71+
return expr;
72+
}
73+
74+
static PgBenchExpr *
75+
make_variable(char *varname)
76+
{
77+
PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
78+
79+
expr->etype = ENODE_VARIABLE;
80+
expr->u.variable.varname = varname;
81+
return expr;
82+
}
83+
84+
static PgBenchExpr *
85+
make_op(char operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr)
86+
{
87+
PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
88+
89+
expr->etype = ENODE_OPERATOR;
90+
expr->u.operator.operator = operator;
91+
expr->u.operator.lexpr = lexpr;
92+
expr->u.operator.rexpr = rexpr;
93+
return expr;
94+
}
95+
96+
#include "exprscan.c"

contrib/pgbench/exprscan.l

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
%{
2+
/*-------------------------------------------------------------------------
3+
*
4+
* exprscan.l
5+
* a lexical scanner for a simple expression syntax
6+
*
7+
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
8+
* Portions Copyright (c) 1994, Regents of the University of California
9+
*
10+
*-------------------------------------------------------------------------
11+
*/
12+
13+
/* line and column number for error reporting */
14+
static int yyline = 0, yycol = 0;
15+
16+
/* Handles to the buffer that the lexer uses internally */
17+
static YY_BUFFER_STATE scanbufhandle;
18+
static char *scanbuf;
19+
static int scanbuflen;
20+
21+
/* flex 2.5.4 doesn't bother with a decl for this */
22+
int expr_yylex(void);
23+
24+
%}
25+
26+
%option 8bit
27+
%option never-interactive
28+
%option nodefault
29+
%option noinput
30+
%option nounput
31+
%option noyywrap
32+
%option warn
33+
%option prefix="expr_yy"
34+
35+
non_newline [^\n\r]
36+
space [ \t\r\f]
37+
38+
%%
39+
40+
"+" { yycol += yyleng; return '+'; }
41+
"-" { yycol += yyleng; return '-'; }
42+
"*" { yycol += yyleng; return '*'; }
43+
"/" { yycol += yyleng; return '/'; }
44+
"%" { yycol += yyleng; return '%'; }
45+
"(" { yycol += yyleng; return '('; }
46+
")" { yycol += yyleng; return ')'; }
47+
:[a-zA-Z0-9_]+ { yycol += yyleng; yylval.str = pg_strdup(yytext + 1); return VARIABLE; }
48+
[0-9]+ { yycol += yyleng; yylval.ival = strtoint64(yytext); return INTEGER; }
49+
50+
[\n] { yycol = 0; yyline++; }
51+
{space} { yycol += yyleng; /* ignore */ }
52+
53+
. {
54+
yycol += yyleng;
55+
fprintf(stderr, "unexpected character '%s'\n", yytext);
56+
return CHAR_ERROR;
57+
}
58+
%%
59+
60+
void
61+
yyerror(const char *message)
62+
{
63+
/* yyline is always 1 as pgbench calls the parser for each line...
64+
* so the interesting location information is the column number */
65+
fprintf(stderr, "%s at column %d\n", message, yycol);
66+
/* go on to raise the error from pgbench with more information */
67+
/* exit(1); */
68+
}
69+
70+
/*
71+
* Called before any actual parsing is done
72+
*/
73+
void
74+
expr_scanner_init(const char *str)
75+
{
76+
Size slen = strlen(str);
77+
78+
/*
79+
* Might be left over after error
80+
*/
81+
if (YY_CURRENT_BUFFER)
82+
yy_delete_buffer(YY_CURRENT_BUFFER);
83+
84+
/*
85+
* Make a scan buffer with special termination needed by flex.
86+
*/
87+
scanbuflen = slen;
88+
scanbuf = pg_malloc(slen + 2);
89+
memcpy(scanbuf, str, slen);
90+
scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
91+
scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
92+
93+
BEGIN(INITIAL);
94+
}
95+
96+
97+
/*
98+
* Called after parsing is done to clean up after seg_scanner_init()
99+
*/
100+
void
101+
expr_scanner_finish(void)
102+
{
103+
yy_delete_buffer(scanbufhandle);
104+
pg_free(scanbuf);
105+
}

0 commit comments

Comments
 (0)