Re: Poorly-thought-out handling of double variables in pgbench - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Poorly-thought-out handling of double variables in pgbench
Date
Msg-id 15599.1462484702@sss.pgh.pa.us
Whole thread Raw
In response to Re: Poorly-thought-out handling of double variables in pgbench  (Fabien COELHO <coelho@cri.ensmp.fr>)
Responses Re: Poorly-thought-out handling of double variables in pgbench
List pgsql-hackers
Fabien COELHO <coelho@cri.ensmp.fr> writes:
>> A better answer, perhaps, would be to store double-valued variables in
>> double format to begin with, coercing to text only when and if the value
>> is interpolated into a string.

> Yep, but that was yet more changes for a limited benefit and would have
> increase the probability that the patch would have been rejected.

>> That's probably a bigger change than we want to be putting in right now,
>> though I'm a bit tempted to go try it.

> I definitely agree that the text variable solution is pretty ugly, but it
> was the minimum change solution, and I do not have much time available.

Well, I felt like doing some minor hacking, so I went and adjusted the
code to work this way.  I'm pretty happy with the result, what do you
think?

            regards, tom lane

diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 2a9063a..a484165 100644
*** a/src/bin/pgbench/pgbench.c
--- b/src/bin/pgbench/pgbench.c
***************
*** 38,43 ****
--- 38,44 ----
  #include "portability/instr_time.h"

  #include <ctype.h>
+ #include <float.h>
  #include <limits.h>
  #include <math.h>
  #include <signal.h>
*************** const char *progname;
*** 185,195 ****

  volatile bool timer_exceeded = false;    /* flag from signal handler */

! /* variable definitions */
  typedef struct
  {
!     char       *name;            /* variable name */
!     char       *value;            /* its value */
  } Variable;

  #define MAX_SCRIPTS        128        /* max number of SQL scripts allowed */
--- 186,205 ----

  volatile bool timer_exceeded = false;    /* flag from signal handler */

! /*
!  * Variable definitions.  If a variable has a string value, "value" is that
!  * value, is_numeric is false, and num_value is undefined.  If the value is
!  * known to be numeric, is_numeric is true and num_value contains the value
!  * (in any permitted numeric variant).  In this case "value" contains the
!  * string equivalent of the number, if we've had occasion to compute that,
!  * or NULL if we haven't.
!  */
  typedef struct
  {
!     char       *name;            /* variable's name */
!     char       *value;            /* its value in string form, if known */
!     bool        is_numeric;        /* is numeric value known? */
!     PgBenchValue num_value;        /* variable's value in numeric form */
  } Variable;

  #define MAX_SCRIPTS        128        /* max number of SQL scripts allowed */
*************** typedef struct
*** 237,243 ****
      bool        throttling;        /* whether nap is for throttling */
      bool        is_throttled;    /* whether transaction throttling is done */
      Variable   *variables;        /* array of variable definitions */
!     int            nvariables;
      int64        txn_scheduled;    /* scheduled start time of transaction (usec) */
      instr_time    txn_begin;        /* used for measuring schedule lag times */
      instr_time    stmt_begin;        /* used for measuring statement latencies */
--- 247,254 ----
      bool        throttling;        /* whether nap is for throttling */
      bool        is_throttled;    /* whether transaction throttling is done */
      Variable   *variables;        /* array of variable definitions */
!     int            nvariables;        /* number of variables */
!     bool        vars_sorted;    /* are variables sorted by name? */
      int64        txn_scheduled;    /* scheduled start time of transaction (usec) */
      instr_time    txn_begin;        /* used for measuring schedule lag times */
      instr_time    stmt_begin;        /* used for measuring statement latencies */
*************** static const BuiltinScript builtin_scrip
*** 363,368 ****
--- 374,381 ----


  /* Function prototypes */
+ static void setIntValue(PgBenchValue *pv, int64 ival);
+ static void setDoubleValue(PgBenchValue *pv, double dval);
  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);
*************** discard_response(CState *state)
*** 836,868 ****
      } while (res);
  }

  static int
! compareVariables(const void *v1, const void *v2)
  {
      return strcmp(((const Variable *) v1)->name,
                    ((const Variable *) v2)->name);
  }

! static char *
! getVariable(CState *st, char *name)
  {
!     Variable    key,
!                *var;

      /* On some versions of Solaris, bsearch of zero items dumps core */
      if (st->nvariables <= 0)
          return NULL;

      key.name = name;
!     var = (Variable *) bsearch((void *) &key,
!                                (void *) st->variables,
!                                st->nvariables,
!                                sizeof(Variable),
!                                compareVariables);
!     if (var != NULL)
!         return var->value;
      else
!         return NULL;
  }

  /* check whether the name consists of alphabets, numerals and underscores. */
--- 849,945 ----
      } while (res);
  }

+ /* qsort comparator for Variable array */
  static int
! compareVariableNames(const void *v1, const void *v2)
  {
      return strcmp(((const Variable *) v1)->name,
                    ((const Variable *) v2)->name);
  }

! /* Locate a variable by name; returns NULL if unknown */
! static Variable *
! lookupVariable(CState *st, char *name)
  {
!     Variable    key;

      /* On some versions of Solaris, bsearch of zero items dumps core */
      if (st->nvariables <= 0)
          return NULL;

+     /* Sort if we have to */
+     if (!st->vars_sorted)
+     {
+         qsort((void *) st->variables, st->nvariables, sizeof(Variable),
+               compareVariableNames);
+         st->vars_sorted = true;
+     }
+
+     /* Now we can search */
      key.name = name;
!     return (Variable *) bsearch((void *) &key,
!                                 (void *) st->variables,
!                                 st->nvariables,
!                                 sizeof(Variable),
!                                 compareVariableNames);
! }
!
! /* Get the value of a variable, in string form; returns NULL if unknown */
! static char *
! getVariable(CState *st, char *name)
! {
!     Variable   *var;
!     char        stringform[64];
!
!     var = lookupVariable(st, name);
!     if (var == NULL)
!         return NULL;            /* not found */
!
!     if (var->value)
!         return var->value;        /* we have it in string form */
!
!     /* We need to produce a string equivalent of the numeric value */
!     Assert(var->is_numeric);
!     if (var->num_value.type == PGBT_INT)
!         snprintf(stringform, sizeof(stringform),
!                  INT64_FORMAT, var->num_value.u.ival);
      else
!     {
!         Assert(var->num_value.type == PGBT_DOUBLE);
!         snprintf(stringform, sizeof(stringform),
!                  "%.*g", DBL_DIG, var->num_value.u.dval);
!     }
!     var->value = pg_strdup(stringform);
!     return var->value;
! }
!
! /* Try to convert variable to numeric form; return false on failure */
! static bool
! makeVariableNumeric(Variable *var)
! {
!     if (var->is_numeric)
!         return true;            /* no work */
!
!     if (is_an_int(var->value))
!     {
!         setIntValue(&var->num_value, strtoint64(var->value));
!         var->is_numeric = true;
!     }
!     else /* type should be double */
!     {
!         double dv;
!
!         if (sscanf(var->value, "%lf", &dv) != 1)
!         {
!             fprintf(stderr,
!                     "malformed variable \"%s\" value: \"%s\"\n",
!                     var->name, var->value);
!             return false;
!         }
!         setDoubleValue(&var->num_value, dv);
!         var->is_numeric = true;
!     }
!     return true;
  }

  /* check whether the name consists of alphabets, numerals and underscores. */
*************** isLegalVariableName(const char *name)
*** 877,902 ****
              return false;
      }

!     return true;
  }

! static int
! putVariable(CState *st, const char *context, char *name, char *value)
  {
!     Variable    key,
!                *var;
!
!     key.name = name;
!     /* On some versions of Solaris, bsearch of zero items dumps core */
!     if (st->nvariables > 0)
!         var = (Variable *) bsearch((void *) &key,
!                                    (void *) st->variables,
!                                    st->nvariables,
!                                    sizeof(Variable),
!                                    compareVariables);
!     else
!         var = NULL;

      if (var == NULL)
      {
          Variable   *newvars;
--- 954,973 ----
              return false;
      }

!     return (i > 0);                /* must be non-empty */
  }

! /*
!  * Lookup a variable by name, creating it if need be.
!  * Caller is expected to assign a value to the variable.
!  * Returns NULL on failure (bad name).
!  */
! static Variable *
! lookupCreateVariable(CState *st, const char *context, char *name)
  {
!     Variable   *var;

+     var = lookupVariable(st, name);
      if (var == NULL)
      {
          Variable   *newvars;
*************** putVariable(CState *st, const char *cont
*** 909,917 ****
          {
              fprintf(stderr, "%s: invalid variable name: \"%s\"\n",
                      context, name);
!             return false;
          }

          if (st->variables)
              newvars = (Variable *) pg_realloc(st->variables,
                                      (st->nvariables + 1) * sizeof(Variable));
--- 980,989 ----
          {
              fprintf(stderr, "%s: invalid variable name: \"%s\"\n",
                      context, name);
!             return NULL;
          }

+         /* Create variable at the end of the array */
          if (st->variables)
              newvars = (Variable *) pg_realloc(st->variables,
                                      (st->nvariables + 1) * sizeof(Variable));
*************** putVariable(CState *st, const char *cont
*** 923,949 ****
          var = &newvars[st->nvariables];

          var->name = pg_strdup(name);
!         var->value = pg_strdup(value);

          st->nvariables++;
!
!         qsort((void *) st->variables, st->nvariables, sizeof(Variable),
!               compareVariables);
      }
-     else
-     {
-         char       *val;

!         /* dup then free, in case value is pointing at this variable */
!         val = pg_strdup(value);

          free(var->value);
!         var->value = val;
!     }

      return true;
  }

  static char *
  parseVariable(const char *sql, int *eaten)
  {
--- 995,1066 ----
          var = &newvars[st->nvariables];

          var->name = pg_strdup(name);
!         var->value = NULL;
!         /* caller is expected to initialize remaining fields */

          st->nvariables++;
!         /* we don't re-sort the array till we have to */
!         st->vars_sorted = false;
      }

!     return var;
! }

+ /* Assign a string value to a variable, creating it if need be */
+ /* Returns false on failure (bad name) */
+ static bool
+ putVariable(CState *st, const char *context, char *name, const char *value)
+ {
+     Variable   *var;
+     char       *val;
+
+     var = lookupCreateVariable(st, context, name);
+     if (!var)
+         return false;
+
+     /* dup then free, in case value is pointing at this variable */
+     val = pg_strdup(value);
+
+     if (var->value)
          free(var->value);
!     var->value = val;
!     var->is_numeric = false;
!
!     return true;
! }
!
! /* Assign a numeric value to a variable, creating it if need be */
! /* Returns false on failure (bad name) */
! static bool
! putVariableNumber(CState *st, const char *context, char *name,
!                   const PgBenchValue *value)
! {
!     Variable   *var;
!
!     var = lookupCreateVariable(st, context, name);
!     if (!var)
!         return false;
!
!     if (var->value)
!         free(var->value);
!     var->value = NULL;
!     var->is_numeric = true;
!     var->num_value = *value;

      return true;
  }

+ /* Assign an integer value to a variable, creating it if need be */
+ /* Returns false on failure (bad name) */
+ static bool
+ putVariableInt(CState *st, const char *context, char *name, int64 value)
+ {
+     PgBenchValue val;
+
+     setIntValue(&val, value);
+     return putVariableNumber(st, context, name, &val);
+ }
+
  static char *
  parseVariable(const char *sql, int *eaten)
  {
*************** evalFunc(TState *thread, CState *st,
*** 1260,1266 ****
                  else
                  {
                      Assert(varg->type == PGBT_DOUBLE);
!                     fprintf(stderr, "double %f\n", varg->u.dval);
                  }

                  *retval = *varg;
--- 1377,1383 ----
                  else
                  {
                      Assert(varg->type == PGBT_DOUBLE);
!                     fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
                  }

                  *retval = *varg;
*************** evaluateExpr(TState *thread, CState *st,
*** 1454,1485 ****

          case ENODE_VARIABLE:
              {
!                 char       *var;

!                 if ((var = getVariable(st, expr->u.variable.varname)) == NULL)
                  {
                      fprintf(stderr, "undefined variable \"%s\"\n",
                              expr->u.variable.varname);
                      return false;
                  }

!                 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;
              }

--- 1571,1589 ----

          case ENODE_VARIABLE:
              {
!                 Variable   *var;

!                 if ((var = lookupVariable(st, expr->u.variable.varname)) == NULL)
                  {
                      fprintf(stderr, "undefined variable \"%s\"\n",
                              expr->u.variable.varname);
                      return false;
                  }

!                 if (!makeVariableNumeric(var))
!                     return false;

+                 *retval = var->num_value;
                  return true;
              }

*************** runShellCommand(CState *st, char *variab
*** 1596,1603 ****
                  argv[0], res);
          return false;
      }
!     snprintf(res, sizeof(res), "%d", retval);
!     if (!putVariable(st, "setshell", variable, res))
          return false;

  #ifdef DEBUG
--- 1700,1706 ----
                  argv[0], res);
          return false;
      }
!     if (!putVariableInt(st, "setshell", variable, retval))
          return false;

  #ifdef DEBUG
*************** top:
*** 1973,1979 ****

          if (pg_strcasecmp(argv[0], "set") == 0)
          {
-             char        res[64];
              PgBenchExpr *expr = commands[st->state]->expr;
              PgBenchValue    result;

--- 2076,2081 ----
*************** top:
*** 1983,1997 ****
                  return true;
              }

!             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))
              {
                  st->ecnt++;
                  return true;
--- 2085,2091 ----
                  return true;
              }

!             if (!putVariableNumber(st, argv[0], argv[1], &result))
              {
                  st->ecnt++;
                  return true;
*************** main(int argc, char **argv)
*** 3325,3332 ****
      PGresult   *res;
      char       *env;

-     char        val[64];
-
      progname = get_progname(argv[0]);

      if (argc > 1)
--- 3419,3424 ----
*************** main(int argc, char **argv)
*** 3767,3774 ****
              state[i].id = i;
              for (j = 0; j < state[0].nvariables; j++)
              {
!                 if (!putVariable(&state[i], "startup", state[0].variables[j].name, state[0].variables[j].value))
!                     exit(1);
              }
          }
      }
--- 3859,3878 ----
              state[i].id = i;
              for (j = 0; j < state[0].nvariables; j++)
              {
!                 Variable   *var = &state[0].variables[j];
!
!                 if (var->is_numeric)
!                 {
!                     if (!putVariableNumber(&state[i], "startup",
!                                      var->name, &var->num_value))
!                         exit(1);
!                 }
!                 else
!                 {
!                     if (!putVariable(&state[i], "startup",
!                                      var->name, var->value))
!                         exit(1);
!                 }
              }
          }
      }
*************** main(int argc, char **argv)
*** 3834,3845 ****
       * :scale variables normally get -s or database scale, but don't override
       * an explicit -D switch
       */
!     if (getVariable(&state[0], "scale") == NULL)
      {
-         snprintf(val, sizeof(val), "%d", scale);
          for (i = 0; i < nclients; i++)
          {
!             if (!putVariable(&state[i], "startup", "scale", val))
                  exit(1);
          }
      }
--- 3938,3948 ----
       * :scale variables normally get -s or database scale, but don't override
       * an explicit -D switch
       */
!     if (lookupVariable(&state[0], "scale") == NULL)
      {
          for (i = 0; i < nclients; i++)
          {
!             if (!putVariableInt(&state[i], "startup", "scale", scale))
                  exit(1);
          }
      }
*************** main(int argc, char **argv)
*** 3848,3859 ****
       * Define a :client_id variable that is unique per connection. But don't
       * override an explicit -D switch.
       */
!     if (getVariable(&state[0], "client_id") == NULL)
      {
          for (i = 0; i < nclients; i++)
          {
!             snprintf(val, sizeof(val), "%d", i);
!             if (!putVariable(&state[i], "startup", "client_id", val))
                  exit(1);
          }
      }
--- 3951,3961 ----
       * Define a :client_id variable that is unique per connection. But don't
       * override an explicit -D switch.
       */
!     if (lookupVariable(&state[0], "client_id") == NULL)
      {
          for (i = 0; i < nclients; i++)
          {
!             if (!putVariableInt(&state[i], "startup", "client_id", i))
                  exit(1);
          }
      }

pgsql-hackers by date:

Previous
From: Greg Stark
Date:
Subject: Re: Is pg_control file crashsafe?
Next
From: Gavin Flower
Date:
Subject: Re: Naming of new tsvector functions