Add -f option which enables to read SQL commands from a file.
authorTatsuo Ishii
Thu, 29 Sep 2005 13:44:25 +0000 (13:44 +0000)
committerTatsuo Ishii
Thu, 29 Sep 2005 13:44:25 +0000 (13:44 +0000)
Patches Contributed by Tomoaki Sato.

contrib/pgbench/README.pgbench
contrib/pgbench/README.pgbench_jis
contrib/pgbench/pgbench.c

index 5ac8bace3fe4e74987bef73a81fd581e9d3a2ef3..d2d57d8b2198e19f4b603f3a6890b258f9a56a7d 100644 (file)
@@ -1,4 +1,4 @@
-pgbench README     2003/11/26 Tatsuo Ishii ([email protected])
+pgbench README     2005/09/29 Tatsuo Ishii
 
 o What is pgbench?
 
@@ -34,16 +34,8 @@ o features of pgbench
 
 o How to install pgbench
 
- (1) Configure and build the standard Postgres distribution.
-
-     You can get away with just running configure at the top level
-     and doing "make all" in src/interfaces/libpq.
-
- (2) Run make in this directory.
-
-     You will see an executable file "pgbench".  You can run it here,
-     or install it with the standard Postgres programs by doing
-     "make install".
+ $make
+ $make install
 
 o How to use pgbench?
 
@@ -124,6 +116,15 @@ o options
    -S
        Perform select only transactions instead of TPC-B.
 
+   -N  Do not update "branches" and "tellers". This will
+           avoid heavy update contention on branches and tellers,
+           while it will not make pgbench supporting TPC-B like
+           transactions.
+
+   -f filename
+       Read transaction script from file. Detailed
+       explanation will appear later.
+
    -C
        Establish connection for each transaction, rather than
        doing it just once at beginning of pgbench in the normal
@@ -158,12 +159,58 @@ o What is the "transaction" actually performed in pgbench?
 
   (7) end;
 
+o -f option
+
+  This supports for reading transaction script from a specified
+  file. This file should include SQL commands in each line. SQL
+  command consists of multiple lines are not supported. Empty lines
+  and lines begging with "--" will be ignored.
+
+  SQL commands can include "meta command" which begins with "\" (back
+  slash). A meta command takes some arguments separted by white
+  spaces. Currently following meta command is supported:
+
+  \setrandom name min max
+
+   assign random integer to name between min and max
+
+  example:
+
+  \setrandom aid 1 100000
+
+  variables can be reffered to in SQL comands by adding ":" in front
+  of the varible name.
+
+  example:
+
+  SELECT abalance FROM accounts WHERE aid = :aid
+
+  For example, TPC-B like benchmark can be defined as follows(scaling
+  factor = 1):
+
+\setrandom aid 1 100000
+\setrandom bid 1 1
+\setrandom tid 1 10
+\setrandom delta 1 10000
+BEGIN
+UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid
+SELECT abalance FROM accounts WHERE aid = :aid
+UPDATE tellers SET tbalance = tbalance + :delta WHERE tid = :tid
+UPDATE branches SET bbalance = bbalance + :delta WHERE bid = :bid
+INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, 'now')
+END
+
 o License?
 
 Basically it is same as BSD license. See pgbench.c for more details.
 
 o History
 
+2005/09/29
+   * add -f option. contributed by Tomoaki Sato.
+
+[updation records were missing]
+
 2003/11/26
    * create indexes after data insertion to reduce time.
      patch from Yutaka Tanida.
index 82a2f6a39bf4b30ce157c2b388f4cd456e00adcd..bafd3e16560b1b7a7e09d1044e16a3a0c452d7ec 100644 (file)
@@ -1,9 +1,9 @@
-pgbench README     2003/11/26 Tatsuo Ishii ([email protected])
+pgbench README     2005/09/29 Tatsuo Ishii
 
 \e$B"#\e(Bpgbench \e$B$H$O!)\e(B
 
-pgbench \e$B$O\e(B TPC-B\e$B$K;w$?%Y%s%A%^!<%/%F%9%H$r9T$J$&%W%m%0%i%`$G$9!%:#$N$H\e(B
-\e$B$3$m\e(B PostgreSQL \e$B@lMQ$G$9!%\e(B
+pgbench \e$B$O%Y%s%A%^!<%/%F%9%H$r9T$J$&%W%m%0%i%`$G$9!%:#$N$H$3$m\e(B 
+PostgreSQL \e$B@lMQ$G$9!%\e(B
 
 pgbench \e$B$O\e(B select/update/insert \e$B$r4^$`%H%i%s%6%/%7%g%s$r\e(B
 \e$B\e(B 1 \e$BIC4V$K\e(B
@@ -31,16 +31,12 @@ o pgbench \e$B$O\e(B libpq \e$B$NHsF14|=hM}5!G=$r;H$C$F%^%k%A%f!<%64D6-$r%7%_%e%l!<
 
 \e$B"#\e(Bpgbench \e$B$N%$%s%9%H!<%k\e(B
 
-(1) PostgreSQL\e$B$r\e(Bconfigure\e$B!$%3%s%Q%$%k$7$^$9!%\e(Bpgbench\e$B$N%$%s%9%H!<%k$@$1\e(B
-    \e$B$,L\E*$G$"$l$P!$\e(BPostgreSQL\e$B$N$9$Y$F$r%3%s%Q%$%k$9$kI,MW$O$"$j$^$;$s!%\e(B
-   PostgreSQL\e$B%=!<%9$N%H%C%W%G%#%l%/%H%j$G\e(Bconfigure\e$B$r$7$?8e!$\e(B
-    src/interface/libpq \e$B$G\e(B "make all" \e$B$r\e(B
+PostgreSQL\e$B$r%3%s%Q%$%k!$%$%s%9%H!<%k$7$?8e\e(B
 
-(2) \e$B$3$N%G%#%l%/%H%j$G\e(B make \e$B$r\e(B"pgbench" \e$B$H$$$&\e(B
-    \e$B\e(B"make
-    install" \e$B$r\e(B PostgreSQL \e$B$NI8=`\e(B
-    (\e$B%G%U%)%k%H$G$O\e(B /usr/local/pgsql/bin) \e$B$K%$%s%9%H!<%k$9$k$3$H$b$G$-\e(B
-    \e$B$^$9!%\e(B
+$ make
+$ make install
+
+\e$B$H$7$^$9!%\e(B
 
 \e$B"#\e(Bpgbench \e$B$N;H$$J}\e(B
 
@@ -104,6 +100,12 @@ pgbench \e$B$K$O$$$m$$$m$J%*%W%7%g%s$,$"$j$^$9!%\e(B
        \e$B$OE,9g$7$J$/$J$j$^$9$,!$$h$j8=\e(B
        \e$B$H$,$G$-$^$9!%\e(B
 
+-f filename    \e$B%H%i%s%6%/%7%g%s$NFbMF$,5-=R$5$l$?%U%!%$%kL>$r;XDj$7$^\e(B
+       \e$B$9!%$3$N%*%W%7%g%s$r;XDj$9$k$H!$%U%!%$%k$K5-=R$5$l$?Fb\e(B
+       \e$BMF$N%H%i%s%6%/%7%g%s$r\e(B
+       \e$BBP>]$H$J$k%G!<%?%Y!<%9$O$"$i$+$8$a=i4|2=$7$F$*$/I,MW$,\e(B
+       \e$B$"$j$^$9!%F~NO%U%)!<%^%C%H$K$D$$$F$O8e=R$7$^$9!%\e(B
+
 -C     \e$B$3$N%*%W%7%g%s$r;XDj$9$k$H!$:G=i$K3NN)$7$?%3%M%/%7%g%s\e(B
        \e$B$r;H$$2s$9$N$G$O$J$/!$3F%H%i%s%6%/%7%g%s$4$H$K\e(BDB\e$B$X$N@\\e(B
        \e$BB3$r9T$$$^$9!%%3%M%/%7%g%s$N%*!<%P!<$X%C%I$rB,Dj$9$k$N\e(B
@@ -176,6 +178,52 @@ pgbench \e$B$G$O!$0J2<$N%7!<%1%s%9$rA4It40N;$7$F\e(B1\e$B%H%i%s%6%/%7%g%s$H?t$($F\e(
 
 (7) end;
 
+\e$B"#F~NO%U%!%$%k$N%U%)!<%^%C%H\e(B
+
+pgbench \e$B$G$O!$\e(B-f \e$B%*%W%7%g%s$r;XDj$7$F%H%i%s%6%/%7%g%s$K4^$^$l$k\e(B SQL \e$B%3\e(B
+\e$B%^%s%I$NFbMF$r5-=R$7$?%U%!%$%k$rFI$_9~$`$3$H$,$G$-$^$9!%F~NO%U%!%$%k$K\e(B
+\e$B$O\e(B 1 \e$B9T$K$D$-\e(B 1 \e$B$D$N%3%^%s%I$r5-=R$7$^$9!%6u9T$OL5;k$5$l!$Fs=E%O%$%U%s\e(B
+\e$B$G;O$^$k9T$O%3%a%s%H$r0UL#$7$^$9!%\e(B
+
+\e$B%3%^%s%I$K$O!$\e(BSQL \e$B%3%^%s%I$K2C$(!$%P%C%/%9%i%C%7%e$G;O$^$k%a%?%3%^%s%I\e(B
+\e$B$r5-=R$G$-$^$9!%%a%?%3%^%s%I$O\e(B pgbench \e$B<+?H$K$h$C$F\e(B
+\e$B%3%^%s%I$N7A<0$O%P%C%/%9%i%C%7%e!$$=$ND>8e$K%3%^%s%I$NF0;l!$$=$N\e(B
+\e$B?t$,B3$-$^$9!%F0;l%3%^%s%I$H0z?t!$$^$?$=$l$>$l$N0z?t$O6uGrJ8;z$K$h$C$F\e(B
+\e$B6h@Z$i$l$^$9!%\e(B
+
+\e$B8=:_$N$H$3$m!$0J2<$N%a%?%3%^%s%I$,Dj5A$5$l$F$$$^$9!%\e(B
+
+\setrandom name min max
+   \e$B:G>.CM\e(B min \e$B$H:GBgCM\e(B max \e$B$N4V$NCM$r\e(Bname \e$BJQ?t$K@_Dj\e(B
+   \e$B$7$^$9!%\e(B
+
+\e$BJQ?t$KMp?t$r@_Dj$9$k$K$O!$\e(B\setrandom \e$B%a%?%3%^%s%I$r;HMQ$7$F0J2<$N$h$&\e(B
+\e$B$K5-=R$7$^$9!%\e(B
+
+\setrandom aid 1 100000
+
+\e$B$3$l$O!$JQ?t\e(B aid \e$B$K\e(B 1 \e$B$+$i\e(B 100000 \e$B$N4V$NMp?t$r@_Dj$7$^$9!%$^$?!$JQ?t$N\e(B
+\e$BCM$r\e(B SQL \e$B%3%^%s%I$KKd$a9~$`$K$O!$0J2<$N$h$&$K$=$NL>A0$NA0$K%3%m%s$rIU\e(B
+\e$B$1$^$9!%\e(B
+
+SELECT abalance FROM accounts WHERE aid = :aid
+
+\e$BNc$($P!$\e(BTCP-B \e$B$KN`;w$7$?%Y%s%A%^!<%/$r7WB,$9$k$K$O!$0J2<$N$h$&$K%H%i%s\e(B
+\e$B%6%/%7%g%s$NFbMF$r%U%!%$%k$K5-=R$7!$\e(B-f \e$B%*%W%7%g%s$K$h$C$F$=$N%U%!%$%k\e(B
+\e$B$r;XDj$7$F\e(B pgbench \e$B$r\e(B
+
+\setrandom aid 1 100000
+\setrandom bid 1 1
+\setrandom tid 1 10
+\setrandom delta 1 10000
+BEGIN
+UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid
+SELECT abalance FROM accounts WHERE aid = :aid
+UPDATE tellers SET tbalance = tbalance + :delta WHERE tid = :tid
+UPDATE branches SET bbalance = bbalance + :delta WHERE bid = :bid
+INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, 'now')
+END
+
 \e$B"#:nr7o\e(B
 
 pgbench \e$B$O@P0f\e(B \e$BC#IW$K$h$C$F=q$+$l$^$7$?!%%i%$%;%s%9>r7o$O\e(B pgbench.c \e$B$N\e(B
@@ -184,6 +232,11 @@ pgbench \e$B$O@P0f\e(B \e$BC#IW$K$h$C$F=q$+$l$^$7$?!%%i%$%;%s%9>r7o$O\e(B pgbench.c
 
 \e$B"#2~DjMzNr\e(B
 
+2005/09/29
+   * \e$B:4F#$5$s$N%Q%C%A$rE,MQ!%\e(B-f \e$B%*%W%7%g%s$NDI2C!%\e(B
+
+[\e$B$3$N4V$$$m$$$mJQ99$,$"$C$?$h$&$@$,\e(BREADME\e$B$O%a%$%s%F%J%s%9$5$l$F$$$J$$\e(B]
+
 2003/11/26
    * \e$BC+ED$5$s$N%Q%C%A$rE,MQ!%\e(Bpgbench -i\e$B$N:]$K!$8e$+$i\e(B
      \e$B$9$k$h$&$K$7$?!%$3$l$K$h$C$F=i4|2=$N\e(B
index 69145c8331a6757ee304c11f8d3f2c126dd98e83..ecfb522c692589da6d21c9e8a594237e3829c37c 100644 (file)
@@ -1,10 +1,10 @@
 /*
- * $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.36 2005/05/24 00:26:40 neilc Exp $
+ * $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.37 2005/09/29 13:44:25 ishii Exp $
  *
  * pgbench: a simple TPC-B like benchmark program for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2000-2004 Tatsuo Ishii
+ * Copyright (c) 2000-2005 Tatsuo Ishii
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -41,6 +41,9 @@
 #include 
 #endif   /* ! WIN32 */
 
+#include 
+#include 
+
 extern char *optarg;
 extern int optind;
 
@@ -72,6 +75,9 @@ int           tps = 1;
 #define ntellers   10
 #define naccounts  100000
 
+#define SQL_COMMAND        1
+#define META_COMMAND   2
+
 FILE      *LOGFILE = NULL;
 
 bool       use_log;            /* log transaction latencies to a file */
@@ -89,6 +95,12 @@ char    *login = NULL;
 char      *pwd = NULL;
 char      *dbName;
 
+typedef struct
+{
+   char       *name;
+   char       *value;
+}  Variable;
+
 typedef struct
 {
    PGconn     *con;            /* connection handle to DB */
@@ -103,13 +115,23 @@ typedef struct
    int         tid;            /* teller id for this transaction */
    int         delta;
    int         abalance;
+   void       *variables;
    struct timeval txn_begin;   /* used for measuring latencies */
 }  CState;
 
+typedef struct
+{
+   int         type;
+   int         argc;
+   char      **argv;
+}  Command;
+
+Command      **commands = NULL;
+
 static void
 usage(void)
 {
-   fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-n][-C][-v][-S][-N][-l][-U login][-P password][-d][dbname]\n");
+   fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-n][-C][-v][-S][-N][-f filename][-l][-U login][-P password][-d][dbname]\n");
    fprintf(stderr, "(initialize mode): pgbench -i [-h hostname][-p port][-s scaling_factor][-U login][-P password][-d][dbname]\n");
 }
 
@@ -190,6 +212,115 @@ check(CState * state, PGresult *res, int n, int good)
    return (0);                 /* OK */
 }
 
+static int
+compareVariables(const void *v1, const void *v2)
+{
+   return strcmp(((Variable *)v1)->name, ((Variable *)v2)->name);
+}
+
+static char *
+getVariable(CState * st, char *name)
+{
+   Variable        key = { name }, *var;
+
+   var = tfind(&key, &st->variables, compareVariables);
+   if (var != NULL)
+       return (*(Variable **)var)->value;
+   else
+       return NULL;
+}
+
+static int
+putVariable(CState * st, char *name, char *value)
+{
+   Variable        key = { name }, *var;
+
+   var = tfind(&key, &st->variables, compareVariables);
+   if (var == NULL)
+   {
+       if ((var = malloc(sizeof(Variable))) == NULL)
+           return false;
+
+       var->name = NULL;
+       var->value = NULL;
+
+       if ((var->name = strdup(name)) == NULL
+           || (var->value = strdup(value)) == NULL
+           || tsearch(var, &st->variables, compareVariables) == NULL)
+       {
+           free(var->name);
+           free(var->value);
+           free(var);
+           return false;
+       }
+   }
+   else
+   {
+       free((*(Variable **)var)->value);
+       if (((*(Variable **)var)->value = strdup(value)) == NULL)
+           return false;
+   }
+
+   return true;
+}
+
+static char *
+assignVariables(CState * st, char *sql)
+{
+   int         i, j;
+   char       *p, *name, *val;
+   void       *tmp;
+
+   i = 0;
+   while ((p = strchr(&sql[i], ':')) != NULL)
+   {
+       i = j = p - sql;
+       do
+           i++;
+       while (isalnum(sql[i]) != 0 || sql[i] == '_');
+       if (i == j + 1)
+           continue;
+
+       if ((name = strndup(&sql[j + 1], i - (j + 1))) == NULL)
+           return NULL;
+       val = getVariable(st, name);
+       free(name);
+       if (val == NULL)
+           continue;
+
+       if (strlen(val) > i - j)
+       {
+           tmp = realloc(sql, strlen(sql) - (i - j) + strlen(val) + 1);
+           if (tmp == NULL)
+           {
+               free(sql);
+               return NULL;
+           }
+           sql = tmp;
+       }
+
+       if (strlen(val) != i - j)
+           memmove(&sql[j + strlen(val)], &sql[i], strlen(&sql[i]) + 1);
+
+       strncpy(&sql[j], val, strlen(val));
+
+       if (strlen(val) < i - j)
+       {
+           tmp = realloc(sql, strlen(sql) + 1);
+           if (tmp == NULL)
+           {
+               free(sql);
+               return NULL;
+           }
+           sql = tmp;
+       }
+
+       i = j + strlen(val);
+   }
+
+   return sql;
+}
+
 /* process a transaction */
 static void
 doOne(CState * state, int n, int debug, int ttype)
@@ -465,6 +596,170 @@ doSelectOnly(CState * state, int n, int debug)
    }
 }
 
+static void
+doCustom(CState * state, int n, int debug)
+{
+   PGresult   *res;
+   CState     *st = &state[n];
+
+   if (st->listen)
+   {                           /* are we receiver? */
+       if (commands[st->state]->type == SQL_COMMAND)
+       {
+           if (debug)
+               fprintf(stderr, "client %d receiving\n", n);
+           if (!PQconsumeInput(st->con))
+           {                       /* there's something wrong */
+               fprintf(stderr, "Client %d aborted in state %d. Probably the backend died while processing.\n", n, st->state);
+               remains--;          /* I've aborted */
+               PQfinish(st->con);
+               st->con = NULL;
+               return;
+           }
+           if (PQisBusy(st->con))
+               return;             /* don't have the whole result yet */
+       }
+
+       /*
+        * transaction finished: record the time it took in the
+        * log
+        */
+       if (use_log && commands[st->state + 1] == NULL)
+       {
+           double      diff;
+           struct timeval now;
+
+           gettimeofday(&now, NULL);
+           diff = (int) (now.tv_sec - st->txn_begin.tv_sec) * 1000000.0 +
+               (int) (now.tv_usec - st->txn_begin.tv_usec);
+
+           fprintf(LOGFILE, "%d %d %.0f\n", st->id, st->cnt, diff);
+       }
+
+       if (commands[st->state]->type == SQL_COMMAND)
+       {
+           res = PQgetResult(st->con);
+           if (strncasecmp(commands[st->state]->argv[0], "select", 6) != 0)
+           {
+               if (check(state, res, n, PGRES_COMMAND_OK))
+                   return;
+           }
+           else
+           {
+               if (check(state, res, n, PGRES_TUPLES_OK))
+                   return;
+           }
+           PQclear(res);
+           discard_response(st);
+       }
+
+       if (commands[st->state + 1] == NULL)
+       {
+           if (is_connect)
+           {
+               PQfinish(st->con);
+               st->con = NULL;
+           }
+
+           if (++st->cnt >= nxacts)
+           {
+               remains--;  /* I'm done */
+               if (st->con != NULL)
+               {
+                   PQfinish(st->con);
+                   st->con = NULL;
+               }
+               return;
+           }
+       }
+
+       /* increment state counter */
+       st->state++;
+       if (commands[st->state] == NULL)
+           st->state = 0;
+   }
+
+   if (st->con == NULL)
+   {
+       if ((st->con = doConnect()) == NULL)
+       {
+           fprintf(stderr, "Client %d aborted in establishing connection.\n",
+                   n);
+           remains--;          /* I've aborted */
+           PQfinish(st->con);
+           st->con = NULL;
+           return;
+       }
+   }
+
+   if (use_log && st->state == 0)
+       gettimeofday(&(st->txn_begin), NULL);
+
+   if (commands[st->state]->type == SQL_COMMAND)
+   {
+       char       *sql;
+
+       if ((sql = strdup(commands[st->state]->argv[0])) == NULL
+           || (sql = assignVariables(st, sql)) == NULL)
+       {
+           fprintf(stderr, "out of memory\n");
+           st->ecnt++;
+           return;
+       }
+
+       if (debug)
+           fprintf(stderr, "client %d sending %s\n", n, sql);
+       if (PQsendQuery(st->con, sql) == 0)
+       {
+           if (debug)
+               fprintf(stderr, "PQsendQuery(%s)failed\n", sql);
+           st->ecnt++;
+       }
+       else
+       {
+           st->listen++;           /* flags that should be listened */
+       }
+   }
+   else if (commands[st->state]->type == META_COMMAND)
+   {
+       int         argc = commands[st->state]->argc, i;
+       char      **argv = commands[st->state]->argv;
+
+       if (debug)
+       {
+           fprintf(stderr, "client %d executing \\%s", n, argv[0]);
+           for (i = 1; i < argc; i++)
+               fprintf(stderr, " %s", argv[i]);
+           fprintf(stderr, "\n");
+       }
+
+       if (strcasecmp(argv[0], "setrandom") == 0)
+       {
+           char       *val;
+
+           if ((val = malloc(strlen(argv[3]) + 1)) == NULL)
+           {
+               fprintf(stderr, "%s: out of memory\n", argv[0]);
+               st->ecnt++;
+               return;
+           }
+
+           sprintf(val, "%d", getrand(atoi(argv[2]), atoi(argv[3])));
+
+           if (putVariable(st, argv[1], val) == false)
+           {
+               fprintf(stderr, "%s: out of memory\n", argv[0]);
+               free(val);
+               st->ecnt++;
+               return;
+           }
+
+           free(val);
+           st->listen++;
+       }
+   }
+}
+
 /* discard connections */
 static void
 disconnect_all(CState * state)
@@ -644,6 +939,160 @@ init(void)
    PQfinish(con);
 }
 
+static int
+process_file(char *filename)
+{
+   const char  delim[] = " \f\n\r\t\v";
+
+   FILE       *fd;
+   int         lineno, i, j;
+   char        buf[BUFSIZ], *p, *tok;
+   void       *tmp;
+
+   if (strcmp(filename, "-") == 0)
+       fd = stdin;
+   else if ((fd = fopen(filename, "r")) == NULL)
+   {
+       fprintf(stderr, "%s: %s\n", strerror(errno), filename);
+       return false;
+   }
+
+   fprintf(stderr, "processing file...\n");
+
+   lineno = 1;
+   i = 0;
+   while (fgets(buf, sizeof(buf), fd) != NULL)
+   {
+       if ((p = strchr(buf, '\n')) != NULL)
+           *p = '\0';
+       p = buf;
+       while (isspace(*p))
+           p++;
+       if (*p == '\0' || strncmp(p, "--", 2) == 0)
+       {
+           lineno++;
+           continue;
+       }
+
+       if ((tmp = realloc(commands, sizeof(Command *) * (i + 1))) == NULL)
+       {
+           i--;
+           goto error;
+       }
+       commands = tmp;
+
+       if ((commands[i] = malloc(sizeof(Command))) == NULL)
+           goto error;
+
+       commands[i]->argv = NULL;
+       commands[i]->argc = 0;
+
+       if (*p == '\\')
+       {
+           commands[i]->type = META_COMMAND;
+
+           j = 0;
+           tok = strtok(++p, delim);
+           while (tok != NULL)
+           {
+               tmp = realloc(commands[i]->argv, sizeof(char *) * (j + 1));
+               if (tmp == NULL)
+                   goto error;
+               commands[i]->argv = tmp;
+
+               if ((commands[i]->argv[j] = strdup(tok)) == NULL)
+                   goto error;
+
+               commands[i]->argc++;
+
+               j++;
+               tok = strtok(NULL, delim);
+           }
+
+           if (strcasecmp(commands[i]->argv[0], "setrandom") == 0)
+           {
+               int         min, max;
+
+               if (commands[i]->argc < 4)
+               {
+                   fprintf(stderr, "%s: %d: \\%s: missing argument\n", filename, lineno, commands[i]->argv[0]);
+                   goto error;
+               }
+
+               for (j = 4; j < commands[i]->argc; j++)
+                   fprintf(stderr, "%s: %d: \\%s: extra argument \"%s\" ignored\n", filename, lineno, commands[i]->argv[0], commands[i]->argv[j]);
+
+               if ((min = atoi(commands[i]->argv[2])) < 0)
+               {
+                   fprintf(stderr, "%s: %d: \\%s: invalid minimum number %s\n", filename, lineno, commands[i]->argv[0], commands[i]->argv[2]);
+                   goto error;
+               }
+
+               if ((max = atoi(commands[i]->argv[3])) < min || max > RAND_MAX)
+               {
+                   fprintf(stderr, "%s: %d: \\%s: invalid maximum number %s\n", filename, lineno, commands[i]->argv[0], commands[i]->argv[3]);
+                   goto error;
+               }
+           }
+           else
+           {
+               fprintf(stderr, "%s: %d: invalid command \\%s\n", filename, lineno, commands[i]->argv[0]);
+               goto error;
+           }
+       }
+       else
+       {
+           commands[i]->type = SQL_COMMAND;
+
+           if ((commands[i]->argv = malloc(sizeof(char *))) == NULL)
+               goto error;
+
+           if ((commands[i]->argv[0] = strdup(p)) == NULL)
+               goto error;
+
+           commands[i]->argc++;
+       }
+
+       i++;
+       lineno++;
+   }
+   fclose(fd);
+
+   if ((tmp = realloc(commands, sizeof(Command *) * (i + 1))) == NULL)
+       goto error;
+   commands = tmp;
+
+   commands[i] = NULL;
+
+   return true;
+
+error:
+   if (errno == ENOMEM)
+       fprintf(stderr, "%s: %d: out of memory\n", filename, lineno);
+
+   fclose(fd);
+
+   if (commands == NULL)
+       return false;
+
+   while (i >= 0)
+   {
+       if (commands[i] != NULL)
+       {
+           for (j = 0; j < commands[i]->argc; j++)
+               free(commands[i]->argv[j]);
+
+           free(commands[i]->argv);
+           free(commands[i]);
+       }
+
+       i--;
+   }
+   free(commands);
+
+   return false;
+}
+
 /* print out results */
 static void
 printResults(
@@ -670,8 +1119,10 @@ printResults(
        s = "TPC-B (sort of)";
    else if (ttype == 2)
        s = "Update only accounts";
-   else
+   else if (ttype == 1)
        s = "SELECT only";
+   else
+       s = "Custom query";
 
    printf("transaction type: %s\n", s);
    printf("scaling factor: %d\n", tps);
@@ -695,6 +1146,7 @@ main(int argc, char **argv)
    int         ttype = 0;      /* transaction type. 0: TPC-B, 1: SELECT
                                 * only, 2: skip update of branches and
                                 * tellers */
+   char       *filename = NULL;
 
    static CState *state;       /* status of clients */
 
@@ -724,7 +1176,7 @@ main(int argc, char **argv)
    else if ((env = getenv("PGUSER")) != NULL && *env != '\0')
        login = env;
 
-   while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSl")) != -1)
+   while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSlf:")) != -1)
    {
        switch (c)
        {
@@ -806,6 +1258,10 @@ main(int argc, char **argv)
            case 'l':
                use_log = true;
                break;
+           case 'f':
+               ttype = 3;
+               filename = optarg;
+               break;
            default:
                usage();
                exit(1);
@@ -868,74 +1324,83 @@ main(int argc, char **argv)
        exit(1);
    }
 
-   /*
-    * get the scaling factor that should be same as count(*) from
-    * branches...
-    */
-   res = PQexec(con, "select count(*) from branches");
-   if (PQresultStatus(res) != PGRES_TUPLES_OK)
+   if (ttype == 3)
    {
-       fprintf(stderr, "%s", PQerrorMessage(con));
-       exit(1);
-   }
-   tps = atoi(PQgetvalue(res, 0, 0));
-   if (tps < 0)
-   {
-       fprintf(stderr, "count(*) from branches invalid (%d)\n", tps);
-       exit(1);
+       PQfinish(con);
+       if (process_file(filename) == false)
+           exit(1);
    }
-   PQclear(res);
-
-   if (!is_no_vacuum)
+   else
    {
-       fprintf(stderr, "starting vacuum...");
-       res = PQexec(con, "vacuum branches");
-       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       /*
+        * get the scaling factor that should be same as count(*) from
+        * branches...
+        */
+       res = PQexec(con, "select count(*) from branches");
+       if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
            fprintf(stderr, "%s", PQerrorMessage(con));
            exit(1);
        }
-       PQclear(res);
-
-       res = PQexec(con, "vacuum tellers");
-       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       tps = atoi(PQgetvalue(res, 0, 0));
+       if (tps < 0)
        {
-           fprintf(stderr, "%s", PQerrorMessage(con));
+           fprintf(stderr, "count(*) from branches invalid (%d)\n", tps);
            exit(1);
        }
        PQclear(res);
 
-       res = PQexec(con, "delete from history");
-       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       if (!is_no_vacuum)
        {
-           fprintf(stderr, "%s", PQerrorMessage(con));
-           exit(1);
-       }
-       PQclear(res);
-       res = PQexec(con, "vacuum history");
-       if (PQresultStatus(res) != PGRES_COMMAND_OK)
-       {
-           fprintf(stderr, "%s", PQerrorMessage(con));
-           exit(1);
-       }
-       PQclear(res);
+           fprintf(stderr, "starting vacuum...");
+           res = PQexec(con, "vacuum branches");
+           if (PQresultStatus(res) != PGRES_COMMAND_OK)
+           {
+               fprintf(stderr, "%s", PQerrorMessage(con));
+               exit(1);
+           }
+           PQclear(res);
 
-       fprintf(stderr, "end.\n");
+           res = PQexec(con, "vacuum tellers");
+           if (PQresultStatus(res) != PGRES_COMMAND_OK)
+           {
+               fprintf(stderr, "%s", PQerrorMessage(con));
+               exit(1);
+           }
+           PQclear(res);
 
-       if (is_full_vacuum)
-       {
-           fprintf(stderr, "starting full vacuum...");
-           res = PQexec(con, "vacuum analyze accounts");
+           res = PQexec(con, "delete from history");
+           if (PQresultStatus(res) != PGRES_COMMAND_OK)
+           {
+               fprintf(stderr, "%s", PQerrorMessage(con));
+               exit(1);
+           }
+           PQclear(res);
+           res = PQexec(con, "vacuum history");
            if (PQresultStatus(res) != PGRES_COMMAND_OK)
            {
                fprintf(stderr, "%s", PQerrorMessage(con));
                exit(1);
            }
            PQclear(res);
+
            fprintf(stderr, "end.\n");
+
+           if (is_full_vacuum)
+           {
+               fprintf(stderr, "starting full vacuum...");
+               res = PQexec(con, "vacuum analyze accounts");
+               if (PQresultStatus(res) != PGRES_COMMAND_OK)
+               {
+                   fprintf(stderr, "%s", PQerrorMessage(con));
+                   exit(1);
+               }
+               PQclear(res);
+               fprintf(stderr, "end.\n");
+           }
        }
+       PQfinish(con);
    }
-   PQfinish(con);
 
    /* set random seed */
    gettimeofday(&tv1, NULL);
@@ -965,6 +1430,8 @@ main(int argc, char **argv)
            doOne(state, i, debug, ttype);
        else if (ttype == 1)
            doSelectOnly(state, i, debug);
+       else if (ttype == 3)
+           doCustom(state, i, debug);
    }
 
    for (;;)
@@ -982,16 +1449,16 @@ main(int argc, char **argv)
 
        FD_ZERO(&input_mask);
 
-       maxsock = 0;
+       maxsock = -1;
        for (i = 0; i < nclients; i++)
        {
-           if (state[i].con)
+           if (state[i].con &&
+               (ttype != 3 || commands[state[i].state]->type != META_COMMAND))
            {
                int         sock = PQsocket(state[i].con);
 
                if (sock < 0)
                {
-                   fprintf(stderr, "Client %d: PQsocket failed\n", i);
                    disconnect_all(state);
                    exit(1);
                }
@@ -1001,36 +1468,43 @@ main(int argc, char **argv)
            }
        }
 
-       if ((nsocks = select(maxsock + 1, &input_mask, (fd_set *) NULL,
-                         (fd_set *) NULL, (struct timeval *) NULL)) < 0)
+       if (maxsock != -1)
        {
-           if (errno == EINTR)
-               continue;
-           /* must be something wrong */
-           disconnect_all(state);
-           fprintf(stderr, "select failed: %s\n", strerror(errno));
-           exit(1);
-       }
-       else if (nsocks == 0)
-       {                       /* timeout */
-           fprintf(stderr, "select timeout\n");
-           for (i = 0; i < nclients; i++)
+           if ((nsocks = select(maxsock + 1, &input_mask, (fd_set *) NULL,
+                             (fd_set *) NULL, (struct timeval *) NULL)) < 0)
            {
-               fprintf(stderr, "client %d:state %d cnt %d ecnt %d listen %d\n",
-                       i, state[i].state, state[i].cnt, state[i].ecnt, state[i].listen);
+               if (errno == EINTR)
+                   continue;
+               /* must be something wrong */
+               disconnect_all(state);
+               fprintf(stderr, "select failed: %s\n", strerror(errno));
+               exit(1);
+           }
+           else if (nsocks == 0)
+           {                       /* timeout */
+               fprintf(stderr, "select timeout\n");
+               for (i = 0; i < nclients; i++)
+               {
+                   fprintf(stderr, "client %d:state %d cnt %d ecnt %d listen %d\n",
+                           i, state[i].state, state[i].cnt, state[i].ecnt, state[i].listen);
+               }
+               exit(0);
            }
-           exit(0);
        }
 
        /* ok, backend returns reply */
        for (i = 0; i < nclients; i++)
        {
-           if (state[i].con && FD_ISSET(PQsocket(state[i].con), &input_mask))
+           if (state[i].con && (FD_ISSET(PQsocket(state[i].con), &input_mask)
+                                || (ttype == 3
+                                    && commands[state[i].state]->type == META_COMMAND)))
            {
                if (ttype == 0 || ttype == 2)
                    doOne(state, i, debug, ttype);
                else if (ttype == 1)
                    doSelectOnly(state, i, debug);
+               else if (ttype == 3)
+                   doCustom(state, i, debug);
            }
        }
    }