pgbench: Support double constants and functions.
authorRobert Haas
Tue, 29 Mar 2016 00:45:57 +0000 (20:45 -0400)
committerRobert Haas
Tue, 29 Mar 2016 00:45:57 +0000 (20:45 -0400)
The new functions are pi(), random(), random_exponential(),
random_gaussian(), and sqrt().  I was worried that this would be
slower than before, but, if anything, it actually turns out to be
slightly faster, because we now express the built-in pgbench scripts
using fewer lines; each \setrandom can be merged into a subsequent
\set.

Fabien Coelho

doc/src/sgml/ref/pgbench.sgml
src/bin/pgbench/exprparse.y
src/bin/pgbench/exprscan.l
src/bin/pgbench/pgbench.c
src/bin/pgbench/pgbench.h

index c6d1454b1e955f67b9920c21f65e813f8b7f6d9f..4ceddae681b63a05bf7f093dfd01cdd41f5dd256 100644 (file)
@@ -815,9 +815,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,
       unary operators (+, -) and binary operators
       (+, -, *, /,
@@ -830,7 +831,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
 
     
    
@@ -850,66 +851,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.
      
 
      
@@ -990,34 +960,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.)
-  
-
  
 
  
@@ -1046,7 +988,7 @@ END;
       
        abs(a)
        same as a
-       integer value
+       integer or double absolute value
        abs(-17)
        17
       
@@ -1054,8 +996,22 @@ END;
        debug(a)
        same as 
        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 [, ... ] )
@@ -1071,9 +1027,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.)
+  
+
  
 
  
@@ -1223,13 +1313,10 @@ tps = 618.764555 (including connections establishing)
 tps = 622.977698 (excluding connections establishing)
 script statistics:
  - 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;
index 4948ff3b8132854d867dad1c33172bb33ef9ab18..877244852dd6eb007e779fab39bd81bf4d8a614e 100644 (file)
@@ -20,6 +20,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(yyscan_t yyscanner, const char *operator,
        PgBenchExpr *lexpr, PgBenchExpr *rexpr);
@@ -38,6 +39,7 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %union
 {
    int64       ival;
+   double      dval;
    char       *str;
    PgBenchExpr *expr;
    PgBenchExprList *elist;
@@ -46,9 +48,10 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type  elist
 %type  expr
 %type  INTEGER function
+%type  DOUBLE
 %type  VARIABLE FUNCTION
 
-%token INTEGER VARIABLE FUNCTION
+%token INTEGER DOUBLE VARIABLE FUNCTION
 
 /* Precedence: lowest to highest */
 %left  '+' '-'
@@ -74,6 +77,7 @@ expr: '(' expr ')'            { $$ = $2; }
    | expr '/' expr         { $$ = make_op(yyscanner, "/", $1, $3); }
    | expr '%' expr         { $$ = make_op(yyscanner, "%", $1, $3); }
    | INTEGER               { $$ = make_integer_constant($1); }
+   | DOUBLE                { $$ = make_double_constant($1); }
    | VARIABLE              { $$ = make_variable($1); }
    | function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
    ;
@@ -88,8 +92,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;
 }
 
@@ -154,6 +170,27 @@ static const struct
    {
        "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
index 7120836f84073ea372564bd33001385b4f727813..d8b706a99bcd28e2672d802ace1f155ed945f83b 100644 (file)
@@ -125,6 +125,11 @@ newline            [\n]
                    yylval->ival = strtoint64(yytext);
                    return INTEGER;
                }
+{digit}+(\.{digit}*)?([eE][-+]?{digit}+)?  {
+                   yycolumn += yyleng;
+                   yylval->dval = atof(yytext);
+                   return DOUBLE;
+               }
 {alpha}{alnum}*    {
                    yylval->str = pg_strdup(yytext);
                    return FUNCTION;
index 4196b0e94b01e390b95c056b56e9b40ed94f7caa..a2df4df20d9c06a92a3955336cc8acf4df12e6ac 100644 (file)
@@ -328,13 +328,10 @@ static const BuiltinScript builtin_script[] =
    {
        "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"
@@ -346,13 +343,10 @@ static const BuiltinScript builtin_script[] =
    {
        "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"
@@ -362,15 +356,14 @@ static const BuiltinScript builtin_script[] =
    {
        "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 bool evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval);
+static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
 static void doLog(TState *thread, CState *st, instr_time *now,
      StatsData *agg, bool skipped, double latency, double lag);
 static void processXactStats(TState *thread, CState *st, instr_time *now,
@@ -446,6 +439,33 @@ usage(void)
           progname, progname);
 }
 
+/* return whether str matches "^\s*[-+]?[0-9]+$" */
+static bool
+is_an_int(const char *str)
+{
+   const char *ptr = str;
+
+   /* skip leading spaces; cast is consistent with strtoint64 */
+   while (*ptr && isspace((unsigned char) *ptr))
+       ptr++;
+
+   /* skip sign */
+   if (*ptr == '+' || *ptr == '-')
+       ptr++;
+
+   /* at least one digit */
+   if (*ptr && !isdigit((unsigned char) *ptr))
+       return false;
+
+   /* eat all digits */
+   while (*ptr && isdigit((unsigned char) *ptr))
+       ptr++;
+
+   /* must have reached end of string */
+   return *ptr == '\0';
+}
+
+
 /*
  * strtoint64 -- convert a string to 64-bit integer
  *
@@ -542,6 +562,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] */
@@ -563,6 +584,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
@@ -1006,6 +1030,62 @@ getQueryParams(CState *st, const Command *command, const char **params)
        params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+/* get a value as an int, tell if there is a problem */
+static bool
+coerceToInt(PgBenchValue *pval, int64 *ival)
+{
+   if (pval->type == PGBT_INT)
+   {
+       *ival = pval->u.ival;
+       return true;
+   }
+   else
+   {
+       double dval = pval->u.dval;
+       Assert(pval->type == PGBT_DOUBLE);
+       if (dval < INT64_MIN || INT64_MAX < dval)
+       {
+           fprintf(stderr, "double to int overflow for %f\n", dval);
+           return false;
+       }
+       *ival = (int64) dval;
+       return true;
+   }
+}
+
+/* get a value as a double, or tell if there is a problem */
+static bool
+coerceToDouble(PgBenchValue *pval, double *dval)
+{
+   if (pval->type == PGBT_DOUBLE)
+   {
+       *dval = pval->u.dval;
+       return true;
+   }
+   else
+   {
+       Assert(pval->type == PGBT_INT);
+       *dval = (double) pval->u.ival;
+       return true;
+   }
+}
+
+/* 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
 
@@ -1013,16 +1093,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];
+   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)
@@ -1035,104 +1115,206 @@ 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) && func != PGBENCH_MOD)
                {
-                   case PGBENCH_ADD:
-                       *retval = lval + rval;
-                       return true;
+                   double ld, rd;
 
-                   case PGBENCH_SUB:
-                       *retval = lval - rval;
-                       return true;
+                   if (!coerceToDouble(lval, &ld) ||
+                       !coerceToDouble(rval, &rd))
+                       return false;
 
-                   case PGBENCH_MUL:
-                       *retval = lval * rval;
-                       return true;
+                   switch (func)
+                   {
+                       case PGBENCH_ADD:
+                           setDoubleValue(retval, ld + rd);
+                           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)
+                       case PGBENCH_SUB:
+                           setDoubleValue(retval, ld - rd);
+                           return true;
+
+                       case PGBENCH_MUL:
+                           setDoubleValue(retval, ld * rd);
+                           return true;
+
+                       case PGBENCH_DIV:
+                           setDoubleValue(retval, ld / rd);
+                           return true;
+
+                       default:
+                           /* cannot get here */
+                           Assert(0);
+                   }
+               }
+               else  /* we have integer operands, or % */
+               {
+                   int64 li, ri;
+
+                   if (!coerceToInt(lval, &li) ||
+                       !coerceToInt(rval, &ri))
+                       return false;
+
+                   switch (func)
+                   {
+                       case PGBENCH_ADD:
+                           setIntValue(retval, li + ri);
+                           return true;
+
+                       case PGBENCH_SUB:
+                           setIntValue(retval, li - ri);
+                           return true;
+
+                       case PGBENCH_MUL:
+                           setIntValue(retval, li * ri);
+                           return true;
+
+                       case PGBENCH_DIV:
+                       case PGBENCH_MOD:
+                           if (ri == 0)
+                           {
+                               fprintf(stderr, "division by zero\n");
+                               return false;
+                           }
+                           /* special handling of -1 divisor */
+                           if (ri == -1)
                            {
-                               /* overflow check (needed for INT64_MIN) */
-                               if (lval == PG_INT64_MIN)
+                               if (func == PGBENCH_DIV)
                                {
-                                   fprintf(stderr, "bigint out of range\n");
-                                   return false;
+                                   /* overflow check (needed for INT64_MIN) */
+                                   if (li == PG_INT64_MIN)
+                                   {
+                                       fprintf(stderr, "bigint out of range\n");
+                                       return false;
+                                   }
+                                   else
+                                       setIntValue(retval, - li);
                                }
                                else
-                                   *retval = -lval;
+                                   setIntValue(retval, 0);
+                               return true;
                            }
-                           else
-                               *retval = 0;
+                           /* else divisor is not -1 */
+                           if (func == PGBENCH_DIV)
+                               setIntValue(retval, li / ri);
+                           else /* func == PGBENCH_MOD */
+                               setIntValue(retval, li % ri);
+
                            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];
+               if (varg->type == PGBT_INT)
+               {
+                   int64 i = varg->u.ival;
+                   setIntValue(retval, i < 0 ? -i : i);
+               }
                else
-                   *retval = iargs[0];
+               {
+                   double d = varg->u.dval;
+                   Assert(varg->type == PGBT_DOUBLE);
+                   setDoubleValue(retval, d < 0.0 ? -d: d);
+               }
 
                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
+               {
+                   Assert(varg->type == PGBT_DOUBLE);
+                   fprintf(stderr, "double %f\n", varg->u.dval);
+               }
+
+               *retval = *varg;
+
+               return true;
+           }
+
+       /* 1 double argument */
+       case PGBENCH_DOUBLE:
+       case PGBENCH_SQRT:
+           {
+               double dval;
                Assert(nargs == 1);
 
-               fprintf(stderr, "debug(script=%d,command=%d): " INT64_FORMAT "\n",
-                       st->use_file, st->state + 1, iargs[0]);
+               if (!coerceToDouble(&vargs[0], &dval))
+                   return false;
+
+               if (func == PGBENCH_SQRT)
+                   dval = sqrt(dval);
+
+               setDoubleValue(retval, dval);
+               return true;
+           }
+
+       /* 1 int argument */
+       case PGBENCH_INT:
+           {
+               int64 ival;
+               Assert(nargs == 1);
 
-               *retval = iargs[0];
+               if (!coerceToInt(&vargs[0], &ival))
+                   return false;
 
+               setIntValue(retval, ival);
                return true;
            }
 
+       /* variable number of int arguments */
        case PGBENCH_MIN:
        case PGBENCH_MAX:
            {
-               int64       extremum = iargs[0];
+               int64       extremum;
                int         i;
-
                Assert(nargs >= 1);
 
+               if (!coerceToInt(&vargs[0], &extremum))
+                   return false;
+
                for (i = 1; i < nargs; i++)
                {
-                   int64       ival = iargs[i];
+                   int64       ival;
+
+                   if (!coerceToInt(&vargs[i], &ival))
+                       return false;
 
                    if (func == PGBENCH_MIN)
                        extremum = extremum < ival ? extremum : ival;
@@ -1140,13 +1322,84 @@ 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, imax;
+           Assert(nargs >= 2);
+
+           if (!coerceToInt(&vargs[0], &imin) ||
+               !coerceToInt(&vargs[1], &imax))
+               return false;
+
+           /* check random range */
+           if (imin > imax)
+           {
+               fprintf(stderr, "empty range given to random\n");
+               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");
+               return false;
+           }
+
+           if (func == PGBENCH_RANDOM)
+           {
+               Assert(nargs == 2);
+               setIntValue(retval, getrand(thread, imin, imax));
+           }
+           else /* gaussian & exponential */
+           {
+               double param;
+               Assert(nargs == 3);
+
+               if (!coerceToDouble(&vargs[2], ¶m))
+                   return false;
+
+               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);
+                       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);
+                       return false;
+                   }
+
+                   setIntValue(retval,
+                               getExponentialRand(thread, imin, imax, param));
+               }
+           }
+
+           return true;
+       }
+
        default:
-           fprintf(stderr, "unexpected function tag: %d\n", func);
-           exit(1);
+           /* cannot get here */
+           Assert(0);
+           /* dead code to avoid a compiler warning */
+           return false;
    }
 }
 
@@ -1157,13 +1410,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;
            }
 
@@ -1177,24 +1430,39 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval)
                            expr->u.variable.varname);
                    return false;
                }
-               *retval = strtoint64(var);
+
+               if (is_an_int(var))
+               {
+                   setIntValue(retval, strtoint64(var));
+               }
+               else /* type should be double */
+               {
+                   double dv;
+                   if (sscanf(var, "%lf", &dv) != 1)
+                   {
+                       fprintf(stderr,
+                               "malformed variable \"%s\" value: \"%s\"\n",
+                               expr->u.variable.varname, var);
+                       return false;
+                   }
+                   setDoubleValue(retval, dv);
+               }
+
                return true;
            }
 
        case ENODE_FUNCTION:
-           return evalFunc(st,
+           return evalFunc(thread, st,
                            expr->u.function.function,
                            expr->u.function.args,
                            retval);
 
        default:
+           /* internal error which should never occur */
            fprintf(stderr, "unexpected enode type in evaluation: %d\n",
                    expr->etype);
            exit(1);
    }
-
-   fprintf(stderr, "bad expression\n");
-   return false;
 }
 
 /*
@@ -1673,6 +1941,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;
@@ -1814,15 +2086,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, result.u.ival);
+           else
+           {
+               Assert(result.type == PGBT_DOUBLE);
+               sprintf(res, "%.18e", result.u.dval);
+           }
 
            if (!putVariable(st, argv[0], argv[1], res))
            {
index 46350aace1009a5c9bb16cda3864156d75a9aa41..7dcb67f52035cfc7b89f9d15522f1e546bb3afcb 100644 (file)
  */
 union YYSTYPE;
 
+/*
+ * 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;
@@ -48,6 +69,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;
@@ -59,10 +87,7 @@ struct PgBenchExpr
    PgBenchExprType etype;
    union
    {
-       struct
-       {
-           int64       ival;
-       }           integer_constant;
+       PgBenchValue    constant;
        struct
        {
            char       *varname;