diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index ade1b53..9d5eb32 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -798,8 +798,11 @@ pgbench options dbname The expression may contain integer constants such as 5432, references to variables :variablename, and expressions composed of unary (-) or binary operators - (+, -, *, /, %) - with their usual associativity, and parentheses. + (+, -, *, /, + %) with their usual associativity, function calls and + parentheses. + shows the available + functions. @@ -965,6 +968,52 @@ f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter)) + + + PgBench Functions + + + + Function + Return Type + Description + Example + Result + + + + + abs(a) + same as a + integer value + abs(-17) + 17 + + + debug(a) + same asa + print to stderr the given argument + debug(5432) + 5432 + + + max(i [, ... ] ) + integer + maximum value + max(5, 4, 3, 2) + 5 + + + min(i [, ... ] ) + integer + minimum value + min(5, 4, 3, 2) + 2 + + + +
+ As an example, the full definition of the built-in TPC-B-like transaction is: diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y index 06ee04b..93c6173 100644 --- a/src/bin/pgbench/exprparse.y +++ b/src/bin/pgbench/exprparse.y @@ -16,10 +16,13 @@ PgBenchExpr *expr_parse_result; +static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list); static PgBenchExpr *make_integer_constant(int64 ival); static PgBenchExpr *make_variable(char *varname); -static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, +static PgBenchExpr *make_op(const char *operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr); +static int find_func(const char *fname); +static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args); %} @@ -31,13 +34,15 @@ static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, int64 ival; char *str; PgBenchExpr *expr; + PgBenchExprList *elist; } +%type elist %type expr -%type INTEGER -%type VARIABLE +%type INTEGER function +%type VARIABLE FUNCTION -%token INTEGER VARIABLE +%token INTEGER VARIABLE FUNCTION %token CHAR_ERROR /* never used, will raise a syntax error */ /* Precedence: lowest to highest */ @@ -49,16 +54,25 @@ static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, result: expr { expr_parse_result = $1; } +elist: { $$ = NULL; } + | expr { $$ = make_elist($1, NULL); } + | elist ',' expr { $$ = make_elist($3, $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); } + | '-' 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); } + | function '(' elist ')'{ $$ = make_func($1, $3); } + ; + +function: FUNCTION { $$ = find_func($1); pg_free($1); } ; %% @@ -68,8 +82,9 @@ make_integer_constant(int64 ival) { PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); - expr->etype = ENODE_INTEGER_CONSTANT; - expr->u.integer_constant.ival = ival; + expr->etype = ENODE_CONSTANT; + expr->u.constant.type = PGBT_INT; + expr->u.constant.u.ival = ival; return expr; } @@ -84,14 +99,128 @@ make_variable(char *varname) } static PgBenchExpr * -make_op(char operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr) +make_op(const char *operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr) +{ + return make_func(find_func(operator), + make_elist(rexpr, make_elist(lexpr, NULL))); +} + +/* + * List of available functions: + * - fname: function name + * - nargs: number of arguments + * -1 is a special value for min & max meaning #args >= 1 + * - tag: function identifier from PgBenchFunction enum + */ +static struct +{ + char * fname; + int nargs; + PgBenchFunction tag; +} PGBENCH_FUNCTIONS[] = { + /* parsed as operators, executed as functions */ + { "+", 2, PGBENCH_ADD }, + { "-", 2, PGBENCH_SUB }, + { "*", 2, PGBENCH_MUL }, + { "/", 2, PGBENCH_DIV }, + { "%", 2, PGBENCH_MOD }, + /* actual functions */ + { "abs", 1, PGBENCH_ABS }, + { "min", -1, PGBENCH_MIN }, + { "max", -1, PGBENCH_MAX }, + { "debug", 1, PGBENCH_DEBUG }, + /* keep as last array element */ + { NULL, 0, 0 } +}; + +/* + * Find a function from its name + * + * return the index of the function from the PGBENCH_FUNCTIONS array + * or fail if the function is unknown. + */ +static int +find_func(const char * fname) +{ + int i = 0; + + while (PGBENCH_FUNCTIONS[i].fname) + { + if (pg_strcasecmp(fname, PGBENCH_FUNCTIONS[i].fname) == 0) + return i; + i++; + } + + expr_yyerror_more("unexpected function name", fname); + + /* not reached */ + return -1; +} + +/* Expression linked list builder */ +static PgBenchExprList * +make_elist(PgBenchExpr *expr, PgBenchExprList *list) +{ + PgBenchExprLink * cons; + + if (list == NULL) + { + list = pg_malloc(sizeof(PgBenchExprList)); + list->head = NULL; + list->tail = NULL; + } + + cons = pg_malloc(sizeof(PgBenchExprLink)); + cons->expr = expr; + cons->next = NULL; + + if (list->head == NULL) + list->head = cons; + else + list->tail->next = cons; + + list->tail = cons; + + return list; +} + +/* Return the length of an expression list */ +static int +elist_length(PgBenchExprList *list) +{ + PgBenchExprLink *link = list != NULL? list->head: NULL; + int len = 0; + + for (; link != NULL; link = link->next) + len++; + + return len; +} + +/* Build function call expression */ +static PgBenchExpr * +make_func(const int fnumber, PgBenchExprList *args) { 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; + Assert(fnumber >= 0); + + if ((PGBENCH_FUNCTIONS[fnumber].nargs >= 0 && + PGBENCH_FUNCTIONS[fnumber].nargs != elist_length(args)) || + /* check at least one arg for min & max */ + (PGBENCH_FUNCTIONS[fnumber].nargs == -1 && + elist_length(args) == 0)) + expr_yyerror_more("unexpected number of arguments", + PGBENCH_FUNCTIONS[fnumber].fname); + + expr->etype = ENODE_FUNCTION; + expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag; + + /* only the link is used, the head/tail is not useful anymore */ + expr->u.function.args = args != NULL? args->head: NULL; + if (args) + pg_free(args); + return expr; } diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l index f1c4c7e..7e851f0 100644 --- a/src/bin/pgbench/exprscan.l +++ b/src/bin/pgbench/exprscan.l @@ -46,6 +46,7 @@ space [ \t\r\f] "%" { yycol += yyleng; return '%'; } "(" { yycol += yyleng; return '('; } ")" { yycol += yyleng; return ')'; } +"," { yycol += yyleng; return ','; } :[a-zA-Z0-9_]+ { yycol += yyleng; @@ -57,8 +58,14 @@ space [ \t\r\f] yylval.ival = strtoint64(yytext); return INTEGER; } +[a-zA-Z0-9_]+ { + yycol += yyleng; + yylval.str = pg_strdup(yytext); + return FUNCTION; + } + +[\n] { yycol = 0; yyline++; /* never occurs, input on one line */ } -[\n] { yycol = 0; yyline++; } {space}+ { yycol += yyleng; /* ignore */ } . { @@ -71,10 +78,16 @@ space [ \t\r\f] %% void -yyerror(const char *message) +expr_yyerror_more(const char *message, const char *more) { syntax_error(expr_source, expr_lineno, expr_full_line, expr_command, - message, NULL, expr_col + yycol); + message, more, expr_col + yycol); +} + +void +yyerror(const char *message) +{ + expr_yyerror_more(message, NULL); } /* @@ -94,6 +107,9 @@ expr_scanner_init(const char *str, const char *source, expr_command = (char *) cmd; expr_col = (int) ecol; + /* reset column count for this scan */ + yycol = 0; + /* * Might be left over after error */ diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 42a4e6b..be70804 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -321,13 +321,10 @@ static struct { "tpcb-like", "", - "\\set nbranches " CppAsString2(nbranches) " * :scale\n" - "\\set ntellers " CppAsString2(ntellers) " * :scale\n" - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" - "\\setrandom bid 1 :nbranches\n" - "\\setrandom tid 1 :ntellers\n" - "\\setrandom delta -5000 5000\n" + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" + "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n" + "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n" + "\\set delta random(-5000, 5000)\n" "BEGIN;\n" "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" @@ -339,13 +336,10 @@ static struct { "simple-update", "", - "\\set nbranches " CppAsString2(nbranches) " * :scale\n" - "\\set ntellers " CppAsString2(ntellers) " * :scale\n" - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" - "\\setrandom bid 1 :nbranches\n" - "\\setrandom tid 1 :ntellers\n" - "\\setrandom delta -5000 5000\n" + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" + "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n" + "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n" + "\\set delta random(-5000, 5000)\n" "BEGIN;\n" "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" @@ -355,13 +349,11 @@ static struct { "select-only", "", - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" } }; - /* Function prototypes */ static void setalarm(int seconds); static void *threadRun(void *arg); @@ -372,6 +364,8 @@ static void doLog(TState *thread, CState *st, instr_time *now, StatsData *agg, bool skipped, double latency, double lag); +static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *); + static void usage(void) { @@ -526,6 +520,7 @@ getExponentialRand(TState *thread, int64 min, int64 max, double parameter) uniform, rand; + /* abort if wrong parameter, but must really be checked beforehand */ Assert(parameter > 0.0); cut = exp(-parameter); /* erand in [0, 1), uniform in (0, 1] */ @@ -547,6 +542,9 @@ getGaussianRand(TState *thread, int64 min, int64 max, double parameter) double stdev; double rand; + /* abort if parameter is too low, but must really be checked beforehand */ + Assert(parameter >= MIN_GAUSSIAN_PARAM); + /* * Get user specified random number from this loop, with -parameter < * stdev <= parameter @@ -991,22 +989,189 @@ 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. + * Recursive evaluation of int or double expressions + * + * Note that currently only integer variables are available, with values + * stored as text. */ + +static int64 +coerceToInt(PgBenchValue *pval) +{ + if (pval->type == PGBT_INT) + return pval->u.ival; + /* stop on internal error */ + fprintf(stderr, "unexpected value type %d\n", pval->type); + exit(1); + return 0; +} + +static void +setIntValue(PgBenchValue *pv, int64 ival) +{ + pv->type = PGBT_INT; + pv->u.ival = ival; +} + static bool -evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) +evalFunc(TState *thread, CState *st, + PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval) +{ + switch (func) + { + case PGBENCH_ADD: + case PGBENCH_SUB: + case PGBENCH_MUL: + case PGBENCH_DIV: + case PGBENCH_MOD: + { + PgBenchValue lval, rval; + + if (!args || !args->next || args->next->next) + /* two arguments only */ + return false; + + if (!evaluateExpr(thread, st, args->expr, &lval)) + return false; + + if (!evaluateExpr(thread, st, args->next->expr, &rval)) + return false; + + if (lval.type == PGBT_INT && rval.type == PGBT_INT) + { + switch (func) + { + case PGBENCH_ADD: + setIntValue(retval, coerceToInt(&lval) + coerceToInt(&rval)); + return true; + + case PGBENCH_SUB: + setIntValue(retval, coerceToInt(&lval) - coerceToInt(&rval)); + return true; + + case PGBENCH_MUL: + setIntValue(retval, coerceToInt(&lval) * coerceToInt(&rval)); + return true; + + case PGBENCH_DIV: + case PGBENCH_MOD: + if (coerceToInt(&rval) == 0) + { + fprintf(stderr, "division by zero\n"); + return false; + } + if (coerceToInt(&rval) == -1) + { + setIntValue(retval, 0); + return true; + } + if (func == PGBENCH_DIV) + setIntValue(retval, coerceToInt(&lval) / coerceToInt(&rval)); + else + setIntValue(retval, coerceToInt(&lval) % coerceToInt(&rval)); + return true; + + default: + /* cannot get here */ + Assert(0); + } + } + else + { + /* cannot get here */ + Assert(0); + } + } + + case PGBENCH_ABS: + { + PgBenchValue arg; + + if (!evaluateExpr(thread, st, args->expr, &arg)) + return false; + + if (arg.type == PGBT_INT) + { + int64 i = coerceToInt(&arg); + setIntValue(retval, i < 0? -i: i); + } + + return true; + } + + case PGBENCH_DEBUG: + { + if (!evaluateExpr(thread, st, args->expr, retval)) + return false; + + fprintf(stderr, "debug(script=%d,command=%d): ", + st->use_file, st->state+1); + + if (retval->type == PGBT_INT) + fprintf(stderr, "int " INT64_FORMAT "\n", retval->u.ival); + else + { + /* internal error */ + fprintf(stderr, "unexpected value type %d\n", retval->type); + return false; + } + + return true; + } + + case PGBENCH_MIN: + case PGBENCH_MAX: + { + int64 val = -1; + bool first = true; + while (args != NULL) + { + PgBenchValue arg; + + if (!evaluateExpr(thread, st, args->expr, &arg)) + return false; + + if (first) + val = coerceToInt(&arg); + else + { + int64 i = coerceToInt(&arg); + if (func == PGBENCH_MIN) + val = val < i? val: i; + else if (func == PGBENCH_MAX) + val = val > i? val: i; + } + + args = args->next; + first = false; + } + + setIntValue(retval, val); + return true; + } + + default: + fprintf(stderr, "unexpected function tag: %d\n", func); + exit(1); + } +} + +/* + * 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(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval) { switch (expr->etype) { - case ENODE_INTEGER_CONSTANT: + case ENODE_CONSTANT: { - *retval = expr->u.integer_constant.ival; + *retval = expr->u.constant; return true; } - case ENODE_VARIABLE: { char *var; @@ -1017,90 +1182,22 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) expr->u.variable.varname); return false; } - *retval = strtoint64(var); + + setIntValue(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; - } - - /* - * INT64_MIN / -1 is problematic, since the result - * can't be represented on a two's-complement machine. - * Some machines produce INT64_MIN, some produce zero, - * some throw an exception. We can dodge the problem - * by recognizing that division by -1 is the same as - * negation. - */ - if (rval == -1) - { - *retval = -lval; - - /* overflow check (needed for INT64_MIN) */ - if (lval == PG_INT64_MIN) - { - fprintf(stderr, "bigint out of range\n"); - return false; - } - } - else - *retval = lval / rval; - - return true; - - case '%': - if (rval == 0) - { - fprintf(stderr, "division by zero\n"); - return false; - } - - /* - * Some machines throw a floating-point exception for - * INT64_MIN % -1. Dodge that problem by noting that - * any value modulo -1 is 0. - */ - if (rval == -1) - *retval = 0; - else - *retval = lval % rval; - - return true; - } - - fprintf(stderr, "bad operator\n"); - return false; - } + case ENODE_FUNCTION: + return evalFunc(thread, st, + expr->u.function.function, + expr->u.function.args, + retval); default: - break; + fprintf(stderr, "unexpected enode type in evaluation: %d\n", + expr->etype); + exit(1); } fprintf(stderr, "bad expression\n"); @@ -1562,6 +1659,10 @@ top: fprintf(stderr, "\n"); } + /* + * Note: this section could be removed, as the same functionnality + * is available through \set xxx random_gaussian(...) + */ if (pg_strcasecmp(argv[0], "setrandom") == 0) { char *var; @@ -1702,15 +1803,20 @@ top: else if (pg_strcasecmp(argv[0], "set") == 0) { char res[64]; - PgBenchExpr *expr = commands[st->state]->expr; - int64 result; + PgBenchExpr *expr = commands[st->state]->expr; + PgBenchValue result; - if (!evaluateExpr(st, expr, &result)) + if (!evaluateExpr(thread, st, expr, &result)) { st->ecnt++; return true; } - sprintf(res, INT64_FORMAT, result); + + if (result.type == PGBT_INT) + sprintf(res, INT64_FORMAT, coerceToInt(&result)); + else + /* cannot happend if evaluateExpr above returned ok */ + Assert(0); if (!putVariable(st, argv[0], argv[1], res)) { diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index 5bb2480..1f7251a 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -11,42 +11,95 @@ #ifndef PGBENCH_H #define PGBENCH_H +/* + * Variable types used in parser. + */ +typedef enum +{ + PGBT_INT + // add other types here +} PgBenchValueType; + +typedef struct +{ + PgBenchValueType type; + union + { + int64 ival; + // add other types here + } u; +} PgBenchValue; + +/* Types of expression nodes */ typedef enum PgBenchExprType { - ENODE_INTEGER_CONSTANT, + ENODE_CONSTANT, ENODE_VARIABLE, - ENODE_OPERATOR + ENODE_FUNCTION } PgBenchExprType; +/* List of operators and callable functions */ +typedef enum PgBenchFunction +{ + PGBENCH_ADD, + PGBENCH_SUB, + PGBENCH_MUL, + PGBENCH_DIV, + PGBENCH_MOD, + PGBENCH_DEBUG, + PGBENCH_ABS, + PGBENCH_MIN, + PGBENCH_MAX, +} PgBenchFunction; + typedef struct PgBenchExpr PgBenchExpr; +typedef struct PgBenchExprLink PgBenchExprLink; +typedef struct PgBenchExprList PgBenchExprList; +/* + * Basic representation of an expression parsed. This can be used as + * different things by the parser as defined by PgBenchExprType: + * - ENODE_CONSTANT, constant integer or double value + * - ENODE_VARIABLE, variable result of \set or \setrandom + * - ENODE_FUNCTION, in-core functions and operators + */ struct PgBenchExpr { PgBenchExprType etype; union { - struct - { - int64 ival; - } integer_constant; + PgBenchValue constant; struct { char *varname; } variable; struct { - char operator; - PgBenchExpr *lexpr; - PgBenchExpr *rexpr; - } operator; + PgBenchFunction function; + PgBenchExprLink *args; + } function; } u; }; +/* List of expression nodes */ +struct PgBenchExprLink +{ + PgBenchExpr *expr; + PgBenchExprLink *next; +}; + +struct PgBenchExprList +{ + PgBenchExprLink *head; + PgBenchExprLink *tail; +}; + 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_yyerror_more(const char *str, const char *more); extern void expr_scanner_init(const char *str, const char *source, const int lineno, const char *line, const char *cmd, const int ecol);