diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index cc80b3f..2133bf7 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -794,9 +794,10 @@ pgbench options dbname - Sets variable varname to an integer value calculated + Sets variable varname to a value calculated from expression. The expression may contain integer constants such as 5432, + double constants such as 3.14159, references to variables :variablename, and expressions composed of unary (-) or binary operators (+, -, *, /, @@ -809,7 +810,7 @@ pgbench options dbname Examples: \set ntellers 10 * :scale -\set aid (1021 * :aid) % (100000 * :scale) + 1 +\set aid (1021 * random(1, 100000 * :scale)) % (100000 * :scale) + 1 @@ -829,66 +830,35 @@ pgbench options dbname - By default, or when uniform is specified, all values in the - range are drawn with equal probability. Specifying gaussian - or exponential options modifies this behavior; each - requires a mandatory parameter which determines the precise shape of the - distribution. - + + + + \setrandom n 1 10 or \setrandom n 1 10 uniform + is equivalent to \set n random(1, 10) and uses a uniform + distribution. + + - - For a Gaussian distribution, the interval is mapped onto a standard - normal distribution (the classical bell-shaped Gaussian curve) truncated - at -parameter on the left and +parameter - on the right. - Values in the middle of the interval are more likely to be drawn. - To be precise, if PHI(x) is the cumulative distribution - function of the standard normal distribution, with mean mu - defined as (max + min) / 2.0, with - - f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) / - (2.0 * PHI(parameter) - 1.0) - - then value i between min and - max inclusive is drawn with probability: - f(i + 0.5) - f(i - 0.5). - Intuitively, the larger parameter, the more - frequently values close to the middle of the interval are drawn, and the - less frequently values close to the min and - max bounds. About 67% of values are drawn from the - middle 1.0 / parameter, that is a relative - 0.5 / parameter around the mean, and 95% in the middle - 2.0 / parameter, that is a relative - 1.0 / parameter around the mean; for instance, if - parameter is 4.0, 67% of values are drawn from the - middle quarter (1.0 / 4.0) of the interval (i.e. from - 3.0 / 8.0 to 5.0 / 8.0) and 95% from - the middle half (2.0 / 4.0) of the interval (second and - third quartiles). The minimum parameter is 2.0 for - performance of the Box-Muller transform. - + + + \setrandom n 1 10 exponential 3.0 is equivalent to + \set n random_exponential(1, 10, 3.0) and uses an + exponential distribution. + + - - For an exponential distribution, parameter - controls the distribution by truncating a quickly-decreasing - exponential distribution at parameter, and then - projecting onto integers between the bounds. - To be precise, with - -f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter)) - - Then value i between min and - max inclusive is drawn with probability: - f(x) - f(x + 1). - Intuitively, the larger parameter, the more - frequently values close to min are accessed, and the - less frequently values close to max are accessed. - The closer to 0 parameter, the flatter (more uniform) - the access distribution. - A crude approximation of the distribution is that the most frequent 1% - values in the range, close to min, are drawn - parameter% of the time. - parameter value must be strictly positive. + + + \setrandom n 1 10 gaussian 2.0 is equivalent to + \set n random_gaussian(1, 10, 2.0), and uses a gaussian + distribution. + + + + + See the documentation of these functions below for further information + about the precise shape of these distributions, depending on the value + of the parameter. @@ -967,34 +937,6 @@ f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter)) - - - As an example, the full definition of the built-in TPC-B-like - transaction is: - - -\set nbranches :scale -\set ntellers 10 * :scale -\set naccounts 100000 * :scale -\setrandom aid 1 :naccounts -\setrandom bid 1 :nbranches -\setrandom tid 1 :ntellers -\setrandom delta -5000 5000 -BEGIN; -UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; -SELECT abalance FROM pgbench_accounts WHERE aid = :aid; -UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; -UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; -INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); -END; - - - This script allows each iteration of the transaction to reference - different, randomly-chosen rows. (This example also shows why it's - important for each client session to have its own variables — - otherwise they'd not be independently touching different rows.) - - @@ -1023,7 +965,7 @@ END; abs(a) same as a - integer value + integer or double absolute value abs(-17) 17 @@ -1031,8 +973,22 @@ END; debug(a) same as a print to stderr the given argument - debug(5432) - 5432 + debug(5432.1) + 5432.1 + + + double(i) + double + cast to double + double(5432) + 5432.0 + + + int(x) + integer + cast to int + int(5.4 + 3.8) + 9 max(i [, ... ] ) @@ -1048,9 +1004,143 @@ END; min(5, 4, 3, 2) 2 + + pi() + double + value of the PI constant + pi() + 3.14159265358979323846 + + + random(lb, ub) + integer + uniformly-distributed random integer in [lb, ub] + random(1, 10) + an integer between 1 and 10 + + + random_exponential(lb, ub, parameter) + integer + exponentially-distributed random integer in [lb, ub], + see below + random_exponential(1, 10, 3.0) + an integer between 1 and 10 + + + random_gaussian(lb, ub, parameter) + integer + gaussian-distributed random integer in [lb, ub], + see below + random_gaussian(1, 10, 2.5) + an integer between 1 and 10 + + + sqrt(x) + double + square root + sqrt(2.0) + 1.414213562 + + + + The random function generates values using a uniform + distribution, that is all the values are drawn within the specified + range with equal probability. The random_exponential and + random_gaussian functions require an additional double + parameter which determines the precise shape of the distribution. + + + + + + For an exponential distribution, parameter + controls the distribution by truncating a quickly-decreasing + exponential distribution at parameter, and then + projecting onto integers between the bounds. + To be precise, with + +f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1 - exp(-parameter)) + + Then value i between min and + max inclusive is drawn with probability: + f(x) - f(x + 1). + + + + Intuitively, the larger the parameter, the more + frequently values close to min are accessed, and the + less frequently values close to max are accessed. + The closer to 0 parameter is, the flatter (more + uniform) the access distribution. + A crude approximation of the distribution is that the most frequent 1% + values in the range, close to min, are drawn + parameter% of the time. + The parameter value must be strictly positive. + + + + + + For a Gaussian distribution, the interval is mapped onto a standard + normal distribution (the classical bell-shaped Gaussian curve) truncated + at -parameter on the left and +parameter + on the right. + Values in the middle of the interval are more likely to be drawn. + To be precise, if PHI(x) is the cumulative distribution + function of the standard normal distribution, with mean mu + defined as (max + min) / 2.0, with + + f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) / + (2.0 * PHI(parameter) - 1) + + then value i between min and + max inclusive is drawn with probability: + f(i + 0.5) - f(i - 0.5). + Intuitively, the larger the parameter, the more + frequently values close to the middle of the interval are drawn, and the + less frequently values close to the min and + max bounds. About 67% of values are drawn from the + middle 1.0 / parameter, that is a relative + 0.5 / parameter around the mean, and 95% in the middle + 2.0 / parameter, that is a relative + 1.0 / parameter around the mean; for instance, if + parameter is 4.0, 67% of values are drawn from the + middle quarter (1.0 / 4.0) of the interval (i.e. from + 3.0 / 8.0 to 5.0 / 8.0) and 95% from + the middle half (2.0 / 4.0) of the interval (second and third + quartiles). The minimum parameter is 2.0 for performance + of the Box-Muller transform. + + + + + + As an example, the full definition of the built-in TPC-B-like + transaction is: + + +\set aid random(1, 100000 * :scale) +\set bid random(1, 1 * :scale) +\set tid random(1, 10 * :scale) +\set delta random(-5000, 5000) +BEGIN; +UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; +SELECT abalance FROM pgbench_accounts WHERE aid = :aid; +UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; +UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; +INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); +END; + + + This script allows each iteration of the transaction to reference + different, randomly-chosen rows. (This example also shows why it's + important for each client session to have its own variables — + otherwise they'd not be independently touching different rows.) + + @@ -1201,13 +1291,10 @@ SQL script 1: <builtin: TPC-B (sort of)> - latency average = 15.844 ms - latency stddev = 2.715 ms - statement latencies in milliseconds: - 0.004386 \set nbranches 1 * :scale - 0.001343 \set ntellers 10 * :scale - 0.001212 \set naccounts 100000 * :scale - 0.001310 \setrandom aid 1 :naccounts - 0.001073 \setrandom bid 1 :nbranches - 0.001005 \setrandom tid 1 :ntellers - 0.001078 \setrandom delta -5000 5000 + 0.002522 \set aid random(1, 100000 * :scale) + 0.005459 \set bid random(1, 1 * :scale) + 0.002348 \set tid random(1, 10 * :scale) + 0.001078 \set delta random(-5000, 5000) 0.326152 BEGIN; 0.603376 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 0.454643 SELECT abalance FROM pgbench_accounts WHERE aid = :aid; diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y index cac4d5e..160f959 100644 --- a/src/bin/pgbench/exprparse.y +++ b/src/bin/pgbench/exprparse.y @@ -18,6 +18,7 @@ PgBenchExpr *expr_parse_result; static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list); static PgBenchExpr *make_integer_constant(int64 ival); +static PgBenchExpr *make_double_constant(double dval); static PgBenchExpr *make_variable(char *varname); static PgBenchExpr *make_op(const char *operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr); @@ -32,6 +33,7 @@ static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args); %union { int64 ival; + double dval; char *str; PgBenchExpr *expr; PgBenchExprList *elist; @@ -40,9 +42,10 @@ static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args); %type elist %type expr %type INTEGER function +%type DOUBLE %type VARIABLE FUNCTION -%token INTEGER VARIABLE FUNCTION +%token INTEGER DOUBLE VARIABLE FUNCTION %token CHAR_ERROR /* never used, will raise a syntax error */ /* Precedence: lowest to highest */ @@ -68,6 +71,7 @@ expr: '(' expr ')' { $$ = $2; } | expr '/' expr { $$ = make_op("/", $1, $3); } | expr '%' expr { $$ = make_op("%", $1, $3); } | INTEGER { $$ = make_integer_constant($1); } + | DOUBLE { $$ = make_double_constant($1); } | VARIABLE { $$ = make_variable($1); } | function '(' elist ')'{ $$ = make_func($1, $3); } ; @@ -82,8 +86,20 @@ 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; +} + +static PgBenchExpr * +make_double_constant(double dval) +{ + PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); + + expr->etype = ENODE_CONSTANT; + expr->u.constant.type = PGBT_DOUBLE; + expr->u.constant.u.dval = dval; return expr; } @@ -128,6 +144,13 @@ static struct { "min", -1, PGBENCH_MIN }, { "max", -1, PGBENCH_MAX }, { "debug", 1, PGBENCH_DEBUG }, + { "pi", 0, PGBENCH_PI }, + { "sqrt", 1, PGBENCH_SQRT }, + { "int", 1, PGBENCH_INT }, + { "double", 1, PGBENCH_DOUBLE }, + { "random", 2, PGBENCH_RANDOM }, + { "random_gaussian", 3, PGBENCH_RANDOM_GAUSSIAN }, + { "random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL }, /* keep as last array element */ { NULL, 0, 0 } }; diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l index fc7615f..67d95a7 100644 --- a/src/bin/pgbench/exprscan.l +++ b/src/bin/pgbench/exprscan.l @@ -61,6 +61,11 @@ space [ \t\r\f] yylval.ival = strtoint64(yytext); return INTEGER; } +{digit}+(\.{digit}*)?([eE][-+]?{digit}+)? { + yycol += yyleng; + yylval.dval = atof(yytext); + return DOUBLE; + } {alpha}{alnum}* { yycol += yyleng; yylval.str = pg_strdup(yytext); diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 8b0b17a..cd83478 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,8 +349,7 @@ 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" } }; @@ -372,7 +365,7 @@ static void doLog(TState *thread, CState *st, instr_time *now, StatsData *agg, bool skipped, double latency, double lag); -static bool evaluateExpr(CState *, PgBenchExpr *, int64 *); +static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *); static void usage(void) @@ -528,6 +521,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] */ @@ -549,6 +543,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 @@ -992,6 +989,65 @@ getQueryParams(CState *st, const Command *command, const char **params) params[i] = getVariable(st, command->argv[i + 1]); } +/* get a value as an int */ +static int64 +coerceToInt(PgBenchValue *pval) +{ + if (pval->type == PGBT_INT) + return pval->u.ival; + else if (pval->type == PGBT_DOUBLE) + { + double dval = pval->u.dval; + if (dval < INT64_MIN || INT64_MAX < dval) + { + /* + * Stop on double->int overflow. + * + * Other options would be to abort the current transaction or + * the running client, but this would have more impact on the code + * for little benefit. + */ + fprintf(stderr, "double to int overflow for %f\n", dval); + exit(1); + } + return (int64) dval; + } + /* stop on internal error */ + fprintf(stderr, "unexpected value type %d\n", pval->type); + exit(1); + return 0; +} + +/* get a value as a double */ +static double +coerceToDouble(PgBenchValue *pval) +{ + if (pval->type == PGBT_DOUBLE) + return pval->u.dval; + else if (pval->type == PGBT_INT) + return (double) pval->u.ival; + /* stop on internal error */ + fprintf(stderr, "unexpected value type %d\n", pval->type); + exit(1); + return 0; +} + +/* assign an integer value */ +static void +setIntValue(PgBenchValue *pv, int64 ival) +{ + pv->type = PGBT_INT; + pv->u.ival = ival; +} + +/* assign a double value */ +static void +setDoubleValue(PgBenchValue *pv, double dval) +{ + pv->type = PGBT_DOUBLE; + pv->u.dval = dval; +} + /* maximum number of function arguments */ #define MAX_FARGS 16 @@ -999,16 +1055,16 @@ getQueryParams(CState *st, const Command *command, const char **params) * Recursive evaluation of functions */ static bool -evalFunc(CState *st, - PgBenchFunction func, PgBenchExprLink *args, int64 *retval) +evalFunc(TState *thread, CState *st, + PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval) { /* evaluate all function arguments */ - int nargs = 0; - int64 iargs[MAX_FARGS]; - PgBenchExprLink *l = args; + int nargs = 0; + PgBenchValue vargs[MAX_FARGS]; + PgBenchExprLink *l = args; for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next) - if (!evaluateExpr(st, l->expr, &iargs[nargs])) + if (!evaluateExpr(thread, st, l->expr, &vargs[nargs])) return false; if (l != NULL) @@ -1021,104 +1077,214 @@ evalFunc(CState *st, /* then evaluate function */ switch (func) { + /* overloaded operators */ case PGBENCH_ADD: case PGBENCH_SUB: case PGBENCH_MUL: case PGBENCH_DIV: case PGBENCH_MOD: { - int64 lval = iargs[0], - rval = iargs[1]; + PgBenchValue *lval = & vargs[0], + *rval = & vargs[1]; Assert(nargs == 2); - switch (func) + /* overloaded type management, double if some double */ + if (lval->type == PGBT_DOUBLE || rval->type == PGBT_DOUBLE) { - case PGBENCH_ADD: - *retval = lval + rval; - return true; + switch (func) + { + case PGBENCH_ADD: + setDoubleValue(retval, + coerceToDouble(lval) + coerceToDouble(rval)); + return true; - case PGBENCH_SUB: - *retval = lval - rval; - return true; + case PGBENCH_SUB: + setDoubleValue(retval, + coerceToDouble(lval) - coerceToDouble(rval)); + return true; + + case PGBENCH_MUL: + setDoubleValue(retval, + coerceToDouble(lval) * coerceToDouble(rval)); + return true; - case PGBENCH_MUL: - *retval = lval * rval; + case PGBENCH_DIV: + setDoubleValue(retval, + coerceToDouble(lval) / coerceToDouble(rval)); + return true; + + case PGBENCH_MOD: /* no overloading for modulo */ + if (coerceToInt(rval) == 0) + { + fprintf(stderr, "division by zero\n"); + return false; + } + if (coerceToInt(rval) == -1) + { + setIntValue(retval, 0); + return true; + } + setIntValue(retval, + coerceToInt(lval) % coerceToInt(rval)); return true; - case PGBENCH_DIV: - case PGBENCH_MOD: - if (rval == 0) - { - fprintf(stderr, "division by zero\n"); - return false; - } - /* special handling of -1 divisor */ - if (rval == -1) - { - if (func == PGBENCH_DIV) + default: + /* cannot get here */ + Assert(0); + } + } + else /* we have integer operators */ + { + 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; + } + /* special handling of -1 divisor */ + if (coerceToInt(rval) == -1) { - /* overflow check (needed for INT64_MIN) */ - if (lval == PG_INT64_MIN) + int64 ilval = coerceToInt(lval); + if (func == PGBENCH_DIV) { - fprintf(stderr, "bigint out of range\n"); - return false; + /* overflow check (needed for INT64_MIN) */ + if (ilval == PG_INT64_MIN) + { + fprintf(stderr, "bigint out of range\n"); + return false; + } + else + setIntValue(retval, - ilval); } else - *retval = -lval; + setIntValue(retval, 0); + return true; } - else - *retval = 0; + /* divisor is not -1 */ + if (func == PGBENCH_DIV) + setIntValue(retval, + coerceToInt(lval) / coerceToInt(rval)); + else /* func == PGBENCH_MOD */ + setIntValue(retval, + coerceToInt(lval) % coerceToInt(rval)); return true; - } - /* divisor is not -1 */ - if (func == PGBENCH_DIV) - *retval = lval / rval; - else /* func == PGBENCH_MOD */ - *retval = lval % rval; - return true; - default: - /* cannot get here */ - Assert(0); + default: + /* cannot get here */ + Assert(0); + } } } + /* no arguments */ + case PGBENCH_PI: + setDoubleValue(retval, M_PI); + return true; + + /* 1 overloaded argument */ case PGBENCH_ABS: { + PgBenchValue *varg = & vargs[0]; + Assert(nargs == 1); - if (iargs[0] < 0) - *retval = -iargs[0]; - else - *retval = iargs[0]; + if (varg->type == PGBT_INT) + { + int64 i = varg->u.ival; + setIntValue(retval, i < 0? -i: i); + } + else if (varg->type == PGBT_DOUBLE) + { + double d = varg->u.dval; + setDoubleValue(retval, d < 0.0 ? -d: d); + } + else /* internal error, should abort? */ + { + fprintf(stderr, "unexpected value type %d\n", retval->type); + return false; + } return true; } case PGBENCH_DEBUG: { + PgBenchValue *varg = & vargs[0]; + + Assert(nargs == 1); + + fprintf(stderr, "debug(script=%d,command=%d): ", + st->use_file, st->state+1); + + if (varg->type == PGBT_INT) + fprintf(stderr, "int "INT64_FORMAT"\n", varg->u.ival); + else if (varg->type == PGBT_DOUBLE) + fprintf(stderr, "double %f\n", varg->u.dval); + else /* internal error, should abort? */ + { + fprintf(stderr, "unexpected value type %d\n", varg->type); + return false; + } + + *retval = *varg; + + return true; + } + + /* 1 double argument */ + case PGBENCH_DOUBLE: + case PGBENCH_SQRT: + { + double dval = coerceToDouble(& vargs[0]); + Assert(nargs == 1); - fprintf(stderr, "debug(script=%d,command=%d): " INT64_FORMAT "\n", - st->use_file, st->state + 1, iargs[0]); + if (func == PGBENCH_SQRT) + dval = sqrt(dval); - *retval = iargs[0]; + setDoubleValue(retval, dval); return true; } + /* 1 int argument */ + case PGBENCH_INT: + + Assert(nargs == 1); + setIntValue(retval, coerceToInt(& vargs[0])); + return true; + + /* variable number of int arguments */ case PGBENCH_MIN: case PGBENCH_MAX: { - int64 extremum = iargs[0]; + int64 extremum = coerceToInt(&vargs[0]); int i; Assert(nargs >= 1); for (i = 1; i < nargs; i++) { - int64 ival = iargs[i]; + int64 ival = coerceToInt(&vargs[i]); if (func == PGBENCH_MIN) extremum = extremum < ival ? extremum : ival; @@ -1126,10 +1292,79 @@ evalFunc(CState *st, extremum = extremum > ival ? extremum : ival; } - *retval = extremum; + setIntValue(retval, extremum); return true; } + /* random functions */ + case PGBENCH_RANDOM: + case PGBENCH_RANDOM_EXPONENTIAL: + case PGBENCH_RANDOM_GAUSSIAN: + { + int64 imin = coerceToInt(&vargs[0]), + imax = coerceToInt(&vargs[1]); + + Assert(nargs >= 2); + + /* check random range */ + if (imin > imax) + { + fprintf(stderr, "empty range given to random\n"); + st->ecnt++; + return false; + } + else if (imax - imin < 0 || (imax - imin) + 1 < 0) + { + /* prevent int overflows in random functions */ + fprintf(stderr, "random range is too large\n"); + st->ecnt++; + return false; + } + + if (func == PGBENCH_RANDOM) + { + Assert(nargs == 2); + setIntValue(retval, getrand(thread, imin, imax)); + } + else /* gaussian & exponential */ + { + double param = coerceToDouble(&vargs[2]); + + Assert(nargs == 3); + + if (func == PGBENCH_RANDOM_GAUSSIAN) + { + if (param < MIN_GAUSSIAN_PARAM) + { + fprintf(stderr, + "gaussian parameter must be at least %f " + "(not %f)\n", MIN_GAUSSIAN_PARAM, param); + st->ecnt++; + return false; + } + + setIntValue(retval, + getGaussianRand(thread, imin, imax, param)); + } + else /* exponential */ + { + if (param <= 0.0) + { + fprintf(stderr, + "exponential parameter must be greater than zero" + " (got %f)\n", param); + st->ecnt++; + return false; + } + + setIntValue(retval, + getExponentialRand(thread, imin, imax, param)); + } + } + + return true; + } + default: fprintf(stderr, "unexpected function tag: %d\n", func); exit(1); @@ -1143,13 +1378,13 @@ evalFunc(CState *st, * the value itself is returned through the retval pointer. */ static bool -evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) +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; } @@ -1163,12 +1398,22 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) expr->u.variable.varname); return false; } - *retval = strtoint64(var); + + /* guess double type (n for "inf", "-inf" and "nan") */ + if (strchr(var, '.') != NULL || strchr(var, 'n') != NULL) + { + double dv; + sscanf(var, "%lf", &dv); + setDoubleValue(retval, dv); + } + else + setIntValue(retval, strtoint64(var)); + return true; } case ENODE_FUNCTION: - return evalFunc(st, + return evalFunc(thread, st, expr->u.function.function, expr->u.function.args, retval); @@ -1638,6 +1883,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; @@ -1779,15 +2028,21 @@ top: { char res[64]; PgBenchExpr *expr = commands[st->state]->expr; - int64 result; + 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 if (result.type == PGBT_DOUBLE) + sprintf(res, "%.18e", coerceToDouble(&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 c6aeb5b..ebe9eef 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -11,10 +11,31 @@ #ifndef PGBENCH_H #define PGBENCH_H +/* + * Variable types used in parser. + */ +typedef enum +{ + PGBT_INT, + PGBT_DOUBLE + // add other types here +} PgBenchValueType; + +typedef struct +{ + PgBenchValueType type; + union + { + int64 ival; + double dval; + // add other types here + } u; +} PgBenchValue; + /* Types of expression nodes */ typedef enum PgBenchExprType { - ENODE_INTEGER_CONSTANT, + ENODE_CONSTANT, ENODE_VARIABLE, ENODE_FUNCTION } PgBenchExprType; @@ -31,6 +52,13 @@ typedef enum PgBenchFunction PGBENCH_ABS, PGBENCH_MIN, PGBENCH_MAX, + PGBENCH_INT, + PGBENCH_DOUBLE, + PGBENCH_PI, + PGBENCH_SQRT, + PGBENCH_RANDOM, + PGBENCH_RANDOM_GAUSSIAN, + PGBENCH_RANDOM_EXPONENTIAL } PgBenchFunction; typedef struct PgBenchExpr PgBenchExpr; @@ -42,10 +70,7 @@ struct PgBenchExpr PgBenchExprType etype; union { - struct - { - int64 ival; - } integer_constant; + PgBenchValue constant; struct { char *varname;