pgbench: Support double constants and functions.
authorRobert Haas <rhaas@postgresql.org>
Tue, 29 Mar 2016 00:45:57 +0000 (20:45 -0400)
committerRobert Haas <rhaas@postgresql.org>
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 <optional> <replaceable>options</> </optional> <replaceable>dbname</>
 
     <listitem>
      <para>
-      Sets variable <replaceable>varname</> to an integer value calculated
+      Sets variable <replaceable>varname</> to a value calculated
       from <replaceable>expression</>.
       The expression may contain integer constants such as <literal>5432</>,
+      double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
       unary operators (<literal>+</>, <literal>-</>) and binary operators
       (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
@@ -830,7 +831,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       Examples:
 <programlisting>
 \set ntellers 10 * :scale
-\set aid (1021 * :aid) % (100000 * :scale) + 1
+\set aid (1021 * random(1, 100000 * :scale)) % (100000 * :scale) + 1
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -850,66 +851,35 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      </para>
 
      <para>
-      By default, or when <literal>uniform</> is specified, all values in the
-      range are drawn with equal probability.  Specifying <literal>gaussian</>
-      or  <literal>exponential</> options modifies this behavior; each
-      requires a mandatory parameter which determines the precise shape of the
-      distribution.
-     </para>
+      <itemizedlist>
+       <listitem>
+        <para>
+         <literal>\setrandom n 1 10</> or <literal>\setrandom n 1 10 uniform</>
+         is equivalent to <literal>\set n random(1, 10)</> and uses a uniform
+         distribution.
+        </para>
+       </listitem>
 
-     <para>
-      For a Gaussian distribution, the interval is mapped onto a standard
-      normal distribution (the classical bell-shaped Gaussian curve) truncated
-      at <literal>-parameter</> on the left and <literal>+parameter</>
-      on the right.
-      Values in the middle of the interval are more likely to be drawn.
-      To be precise, if <literal>PHI(x)</> is the cumulative distribution
-      function of the standard normal distribution, with mean <literal>mu</>
-      defined as <literal>(max + min) / 2.0</>, with
-<literallayout>
- f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
-        (2.0 * PHI(parameter) - 1.0)
-</literallayout>
-      then value <replaceable>i</> between <replaceable>min</> and
-      <replaceable>max</> inclusive is drawn with probability:
-      <literal>f(i + 0.5) - f(i - 0.5)</>.
-      Intuitively, the larger <replaceable>parameter</>, the more
-      frequently values close to the middle of the interval are drawn, and the
-      less frequently values close to the <replaceable>min</> and
-      <replaceable>max</> bounds. About 67% of values are drawn from the
-      middle <literal>1.0 / parameter</>, that is a relative
-      <literal>0.5 / parameter</> around the mean, and 95% in the middle
-      <literal>2.0 / parameter</>, that is a relative
-      <literal>1.0 / parameter</> around the mean; for instance, if
-      <replaceable>parameter</> is 4.0, 67% of values are drawn from the
-      middle quarter (1.0 / 4.0) of the interval (i.e. from
-      <literal>3.0 / 8.0</> to <literal>5.0 / 8.0</>) and 95% from
-      the middle half (<literal>2.0 / 4.0</>) of the interval (second and
-      third quartiles). The minimum <replaceable>parameter</> is 2.0 for
-      performance of the Box-Muller transform.
-     </para>
+      <listitem>
+       <para>
+        <literal>\setrandom n 1 10 exponential 3.0</> is equivalent to
+        <literal>\set n random_exponential(1, 10, 3.0)</> and uses an
+        exponential distribution.
+       </para>
+      </listitem>
 
-     <para>
-      For an exponential distribution, <replaceable>parameter</>
-      controls the distribution by truncating a quickly-decreasing
-      exponential distribution at <replaceable>parameter</>, and then
-      projecting onto integers between the bounds.
-      To be precise, with
-<literallayout>
-f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter))
-</literallayout>
-      Then value <replaceable>i</> between <replaceable>min</> and
-      <replaceable>max</> inclusive is drawn with probability:
-      <literal>f(x) - f(x + 1)</>.
-      Intuitively, the larger <replaceable>parameter</>, the more
-      frequently values close to <replaceable>min</> are accessed, and the
-      less frequently values close to <replaceable>max</> are accessed.
-      The closer to 0 <replaceable>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 <replaceable>min</>, are drawn
-      <replaceable>parameter</>% of the time.
-      <replaceable>parameter</> value must be strictly positive.
+      <listitem>
+       <para>
+        <literal>\setrandom n 1 10 gaussian 2.0</> is equivalent to
+        <literal>\set n random_gaussian(1, 10, 2.0)</>, and uses a gaussian
+        distribution.
+       </para>
+      </listitem>
+     </itemizedlist>
+
+       See the documentation of these functions below for further information
+       about the precise shape of these distributions, depending on the value
+       of the parameter.
      </para>
 
      <para>
@@ -990,34 +960,6 @@ f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter))
     </listitem>
    </varlistentry>
   </variablelist>
-
-  <para>
-   As an example, the full definition of the built-in TPC-B-like
-   transaction is:
-
-<programlisting>
-\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;
-</programlisting>
-
-   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 &mdash;
-   otherwise they'd not be independently touching different rows.)
-  </para>
-
  </refsect2>
 
  <refsect2 id="pgbench-builtin-functions">
@@ -1046,7 +988,7 @@ END;
       <row>
        <entry><literal><function>abs(<replaceable>a</>)</></></>
        <entry>same as <replaceable>a</></>
-       <entry>integer value</>
+       <entry>integer or double absolute value</>
        <entry><literal>abs(-17)</></>
        <entry><literal>17</></>
       </row>
@@ -1054,8 +996,22 @@ END;
        <entry><literal><function>debug(<replaceable>a</>)</></></>
        <entry>same as <replaceable>a</> </>
        <entry>print to <systemitem>stderr</systemitem> the given argument</>
-       <entry><literal>debug(5432)</></>
-       <entry><literal>5432</></>
+       <entry><literal>debug(5432.1)</></>
+       <entry><literal>5432.1</></>
+      </row>
+      <row>
+       <entry><literal><function>double(<replaceable>i</>)</></></>
+       <entry>double</>
+       <entry>cast to double</>
+       <entry><literal>double(5432)</></>
+       <entry><literal>5432.0</></>
+      </row>
+      <row>
+       <entry><literal><function>int(<replaceable>x</>)</></></>
+       <entry>integer</>
+       <entry>cast to int</>
+       <entry><literal>int(5.4 + 3.8)</></>
+       <entry><literal>9</></>
       </row>
       <row>
        <entry><literal><function>max(<replaceable>i</> [, <replaceable>...</> ] )</></></>
@@ -1071,9 +1027,143 @@ END;
        <entry><literal>min(5, 4, 3, 2)</></>
        <entry><literal>2</></>
       </row>
+      <row>
+       <entry><literal><function>pi()</></></>
+       <entry>double</>
+       <entry>value of the PI constant</>
+       <entry><literal>pi()</></>
+       <entry><literal>3.14159265358979323846</></>
+      </row>
+      <row>
+       <entry><literal><function>random(<replaceable>lb</>, <replaceable>ub</>)</></></>
+       <entry>integer</>
+       <entry>uniformly-distributed random integer in <literal>[lb, ub]</></>
+       <entry><literal>random(1, 10)</></>
+       <entry>an integer between <literal>1</> and <literal>10</></>
+      </row>
+      <row>
+       <entry><literal><function>random_exponential(<replaceable>lb</>, <replaceable>ub</>, <replaceable>parameter</>)</></></>
+       <entry>integer</>
+       <entry>exponentially-distributed random integer in <literal>[lb, ub]</>,
+              see below</>
+       <entry><literal>random_exponential(1, 10, 3.0)</></>
+       <entry>an integer between <literal>1</> and <literal>10</></>
+      </row>
+      <row>
+       <entry><literal><function>random_gaussian(<replaceable>lb</>, <replaceable>ub</>, <replaceable>parameter</>)</></></>
+       <entry>integer</>
+       <entry>gaussian-distributed random integer in <literal>[lb, ub]</>,
+              see below</>
+       <entry><literal>random_gaussian(1, 10, 2.5)</></>
+       <entry>an integer between <literal>1</> and <literal>10</></>
+      </row>
+      <row>
+       <entry><literal><function>sqrt(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>square root</>
+       <entry><literal>sqrt(2.0)</></>
+       <entry><literal>1.414213562</></>
+      </row>
      </tbody>
      </tgroup>
    </table>
+
+   <para>
+    The <literal>random</> function generates values using a uniform
+    distribution, that is all the values are drawn within the specified
+    range with equal probability. The <literal>random_exponential</> and
+    <literal>random_gaussian</> functions require an additional double
+    parameter which determines the precise shape of the distribution.
+   </para>
+
+   <itemizedlist>
+    <listitem>
+     <para>
+      For an exponential distribution, <replaceable>parameter</>
+      controls the distribution by truncating a quickly-decreasing
+      exponential distribution at <replaceable>parameter</>, and then
+      projecting onto integers between the bounds.
+      To be precise, with
+<literallayout>
+f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1 - exp(-parameter))
+</literallayout>
+      Then value <replaceable>i</> between <replaceable>min</> and
+      <replaceable>max</> inclusive is drawn with probability:
+      <literal>f(x) - f(x + 1)</>.
+     </para>
+
+     <para>
+      Intuitively, the larger the <replaceable>parameter</>, the more
+      frequently values close to <replaceable>min</> are accessed, and the
+      less frequently values close to <replaceable>max</> are accessed.
+      The closer to 0 <replaceable>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 <replaceable>min</>, are drawn
+      <replaceable>parameter</>% of the time.
+      The <replaceable>parameter</> value must be strictly positive.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      For a Gaussian distribution, the interval is mapped onto a standard
+      normal distribution (the classical bell-shaped Gaussian curve) truncated
+      at <literal>-parameter</> on the left and <literal>+parameter</>
+      on the right.
+      Values in the middle of the interval are more likely to be drawn.
+      To be precise, if <literal>PHI(x)</> is the cumulative distribution
+      function of the standard normal distribution, with mean <literal>mu</>
+      defined as <literal>(max + min) / 2.0</>, with
+<literallayout>
+ f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
+        (2.0 * PHI(parameter) - 1)
+</literallayout>
+      then value <replaceable>i</> between <replaceable>min</> and
+      <replaceable>max</> inclusive is drawn with probability:
+      <literal>f(i + 0.5) - f(i - 0.5)</>.
+      Intuitively, the larger the <replaceable>parameter</>, the more
+      frequently values close to the middle of the interval are drawn, and the
+      less frequently values close to the <replaceable>min</> and
+      <replaceable>max</> bounds. About 67% of values are drawn from the
+      middle <literal>1.0 / parameter</>, that is a relative
+      <literal>0.5 / parameter</> around the mean, and 95% in the middle
+      <literal>2.0 / parameter</>, that is a relative
+      <literal>1.0 / parameter</> around the mean; for instance, if
+      <replaceable>parameter</> is 4.0, 67% of values are drawn from the
+      middle quarter (1.0 / 4.0) of the interval (i.e. from
+      <literal>3.0 / 8.0</> to <literal>5.0 / 8.0</>) and 95% from
+      the middle half (<literal>2.0 / 4.0</>) of the interval (second and third
+      quartiles). The minimum <replaceable>parameter</> is 2.0 for performance
+      of the Box-Muller transform.
+     </para>
+    </listitem>
+   </itemizedlist>
+
+  <para>
+   As an example, the full definition of the built-in TPC-B-like
+   transaction is:
+
+<programlisting>
+\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;
+</programlisting>
+
+   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 &mdash;
+   otherwise they'd not be independently touching different rows.)
+  </para>
+
  </refsect2>
 
  <refsect2>
@@ -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> elist
 %type <expr> expr
 %type <ival> INTEGER function
+%type <dval> DOUBLE
 %type <str> 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",
                "<builtin: TPC-B (sort of)>",
-               "\\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",
                "<builtin: 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",
                "<builtin: 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], &param))
+                                       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;