/src/bin/pgbench/pgbench.c
C | 6094 lines | 4299 code | 787 blank | 1008 comment | 1030 complexity | 582d1a68b5374ea7045beecb09405bd8 MD5 | raw file
Possible License(s): AGPL-3.0
Large files files are truncated, but you can click here to view the full file
- /*
- * pgbench.c
- *
- * A simple benchmark program for PostgreSQL
- * Originally written by Tatsuo Ishii and enhanced by many contributors.
- *
- * src/bin/pgbench/pgbench.c
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
- * ALL RIGHTS RESERVED;
- *
- * Permission to use, copy, modify, and distribute this software and its
- * documentation for any purpose, without fee, and without a written agreement
- * is hereby granted, provided that the above copyright notice and this
- * paragraph and the following two paragraphs appear in all copies.
- *
- * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
- * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
- * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
- * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
- * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
- * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
- *
- */
- #ifdef WIN32
- #define FD_SETSIZE 1024 /* set before winsock2.h is included */
- #endif /* ! WIN32 */
- #include "postgres_fe.h"
- #include "fe_utils/conditional.h"
- #include "getopt_long.h"
- #include "libpq-fe.h"
- #include "portability/instr_time.h"
- #include <ctype.h>
- #include <float.h>
- #include <limits.h>
- #include <math.h>
- #include <signal.h>
- #include <time.h>
- #include <sys/time.h>
- #ifdef HAVE_SYS_SELECT_H
- #include <sys/select.h>
- #endif
- #ifdef HAVE_SYS_RESOURCE_H
- #include <sys/resource.h> /* for getrlimit */
- #endif
- #ifndef M_PI
- #define M_PI 3.14159265358979323846
- #endif
- #include "pgbench.h"
- #define ERRCODE_UNDEFINED_TABLE "42P01"
- /*
- * Hashing constants
- */
- #define FNV_PRIME UINT64CONST(0x100000001b3)
- #define FNV_OFFSET_BASIS UINT64CONST(0xcbf29ce484222325)
- #define MM2_MUL UINT64CONST(0xc6a4a7935bd1e995)
- #define MM2_MUL_TIMES_8 UINT64CONST(0x35253c9ade8f4ca8)
- #define MM2_ROT 47
- /*
- * Multi-platform pthread implementations
- */
- #ifdef WIN32
- /* Use native win32 threads on Windows */
- typedef struct win32_pthread *pthread_t;
- typedef int pthread_attr_t;
- static int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- static int pthread_join(pthread_t th, void **thread_return);
- #elif defined(ENABLE_THREAD_SAFETY)
- /* Use platform-dependent pthread capability */
- #include <pthread.h>
- #else
- /* No threads implementation, use none (-j 1) */
- #define pthread_t void *
- #endif
- /********************************************************************
- * some configurable parameters */
- /* max number of clients allowed */
- #ifdef FD_SETSIZE
- #define MAXCLIENTS (FD_SETSIZE - 10)
- #else
- #define MAXCLIENTS 1024
- #endif
- #define DEFAULT_INIT_STEPS "dtgvp" /* default -I setting */
- #define LOG_STEP_SECONDS 5 /* seconds between log messages */
- #define DEFAULT_NXACTS 10 /* default nxacts */
- #define ZIPF_CACHE_SIZE 15 /* cache cells number */
- #define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */
- #define MAX_ZIPFIAN_PARAM 1000 /* maximum parameter for zipfian */
- int nxacts = 0; /* number of transactions per client */
- int duration = 0; /* duration in seconds */
- int64 end_time = 0; /* when to stop in micro seconds, under -T */
- /*
- * scaling factor. for example, scale = 10 will make 1000000 tuples in
- * pgbench_accounts table.
- */
- int scale = 1;
- /*
- * fillfactor. for example, fillfactor = 90 will use only 90 percent
- * space during inserts and leave 10 percent free.
- */
- int fillfactor = 100;
- /*
- * use unlogged tables?
- */
- bool unlogged_tables = false;
- /*
- * log sampling rate (1.0 = log everything, 0.0 = option not given)
- */
- double sample_rate = 0.0;
- /*
- * When threads are throttled to a given rate limit, this is the target delay
- * to reach that rate in usec. 0 is the default and means no throttling.
- */
- int64 throttle_delay = 0;
- /*
- * Transactions which take longer than this limit (in usec) are counted as
- * late, and reported as such, although they are completed anyway. When
- * throttling is enabled, execution time slots that are more than this late
- * are skipped altogether, and counted separately.
- */
- int64 latency_limit = 0;
- /*
- * tablespace selection
- */
- char *tablespace = NULL;
- char *index_tablespace = NULL;
- /* random seed used when calling srandom() */
- int64 random_seed = -1;
- /*
- * end of configurable parameters
- *********************************************************************/
- #define nbranches 1 /* Makes little sense to change this. Change
- * -s instead */
- #define ntellers 10
- #define naccounts 100000
- /*
- * The scale factor at/beyond which 32bit integers are incapable of storing
- * 64bit values.
- *
- * Although the actual threshold is 21474, we use 20000 because it is easier to
- * document and remember, and isn't that far away from the real threshold.
- */
- #define SCALE_32BIT_THRESHOLD 20000
- bool use_log; /* log transaction latencies to a file */
- bool use_quiet; /* quiet logging onto stderr */
- int agg_interval; /* log aggregates instead of individual
- * transactions */
- bool per_script_stats = false; /* whether to collect stats per script */
- int progress = 0; /* thread progress report every this seconds */
- bool progress_timestamp = false; /* progress report with Unix time */
- int nclients = 1; /* number of clients */
- int nthreads = 1; /* number of threads */
- bool is_connect; /* establish connection for each transaction */
- bool is_latencies; /* report per-command latencies */
- int main_pid; /* main process id used in log filename */
- char *pghost = "";
- char *pgport = "";
- char *login = NULL;
- char *dbName;
- char *logfile_prefix = NULL;
- const char *progname;
- #define WSEP '@' /* weight separator */
- volatile bool timer_exceeded = false; /* flag from signal handler */
- /*
- * Variable definitions.
- *
- * If a variable only has a string value, "svalue" is that value, and value is
- * "not set". If the value is known, "value" contains the value (in any
- * variant).
- *
- * In this case "svalue" contains the string equivalent of the value, if we've
- * had occasion to compute that, or NULL if we haven't.
- */
- typedef struct
- {
- char *name; /* variable's name */
- char *svalue; /* its value in string form, if known */
- PgBenchValue value; /* actual variable's value */
- } Variable;
- #define MAX_SCRIPTS 128 /* max number of SQL scripts allowed */
- #define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */
- /*
- * Simple data structure to keep stats about something.
- *
- * XXX probably the first value should be kept and used as an offset for
- * better numerical stability...
- */
- typedef struct SimpleStats
- {
- int64 count; /* how many values were encountered */
- double min; /* the minimum seen */
- double max; /* the maximum seen */
- double sum; /* sum of values */
- double sum2; /* sum of squared values */
- } SimpleStats;
- /*
- * Data structure to hold various statistics: per-thread and per-script stats
- * are maintained and merged together.
- */
- typedef struct StatsData
- {
- time_t start_time; /* interval start time, for aggregates */
- int64 cnt; /* number of transactions, including skipped */
- int64 skipped; /* number of transactions skipped under --rate
- * and --latency-limit */
- SimpleStats latency;
- SimpleStats lag;
- } StatsData;
- /*
- * Connection state machine states.
- */
- typedef enum
- {
- /*
- * The client must first choose a script to execute. Once chosen, it can
- * either be throttled (state CSTATE_START_THROTTLE under --rate) or start
- * right away (state CSTATE_START_TX).
- */
- CSTATE_CHOOSE_SCRIPT,
- /*
- * In CSTATE_START_THROTTLE state, we calculate when to begin the next
- * transaction, and advance to CSTATE_THROTTLE. CSTATE_THROTTLE state
- * sleeps until that moment. (If throttling is not enabled, doCustom()
- * falls directly through from CSTATE_START_THROTTLE to CSTATE_START_TX.)
- */
- CSTATE_START_THROTTLE,
- CSTATE_THROTTLE,
- /*
- * CSTATE_START_TX performs start-of-transaction processing. Establishes
- * a new connection for the transaction, in --connect mode, and records
- * the transaction start time.
- */
- CSTATE_START_TX,
- /*
- * We loop through these states, to process each command in the script:
- *
- * CSTATE_START_COMMAND starts the execution of a command. On a SQL
- * command, the command is sent to the server, and we move to
- * CSTATE_WAIT_RESULT state. On a \sleep meta-command, the timer is set,
- * and we enter the CSTATE_SLEEP state to wait for it to expire. Other
- * meta-commands are executed immediately.
- *
- * CSTATE_SKIP_COMMAND for conditional branches which are not executed,
- * quickly skip commands that do not need any evaluation.
- *
- * CSTATE_WAIT_RESULT waits until we get a result set back from the server
- * for the current command.
- *
- * CSTATE_SLEEP waits until the end of \sleep.
- *
- * CSTATE_END_COMMAND records the end-of-command timestamp, increments the
- * command counter, and loops back to CSTATE_START_COMMAND state.
- */
- CSTATE_START_COMMAND,
- CSTATE_SKIP_COMMAND,
- CSTATE_WAIT_RESULT,
- CSTATE_SLEEP,
- CSTATE_END_COMMAND,
- /*
- * CSTATE_END_TX performs end-of-transaction processing. Calculates
- * latency, and logs the transaction. In --connect mode, closes the
- * current connection. Chooses the next script to execute and starts over
- * in CSTATE_START_THROTTLE state, or enters CSTATE_FINISHED if we have no
- * more work to do.
- */
- CSTATE_END_TX,
- /*
- * Final states. CSTATE_ABORTED means that the script execution was
- * aborted because a command failed, CSTATE_FINISHED means success.
- */
- CSTATE_ABORTED,
- CSTATE_FINISHED
- } ConnectionStateEnum;
- /*
- * Connection state.
- */
- typedef struct
- {
- PGconn *con; /* connection handle to DB */
- int id; /* client No. */
- ConnectionStateEnum state; /* state machine's current state. */
- ConditionalStack cstack; /* enclosing conditionals state */
- int use_file; /* index in sql_script for this client */
- int command; /* command number in script */
- /* client variables */
- Variable *variables; /* array of variable definitions */
- int nvariables; /* number of variables */
- bool vars_sorted; /* are variables sorted by name? */
- /* various times about current transaction */
- int64 txn_scheduled; /* scheduled start time of transaction (usec) */
- int64 sleep_until; /* scheduled start time of next cmd (usec) */
- instr_time txn_begin; /* used for measuring schedule lag times */
- instr_time stmt_begin; /* used for measuring statement latencies */
- bool prepared[MAX_SCRIPTS]; /* whether client prepared the script */
- /* per client collected stats */
- int64 cnt; /* client transaction count, for -t */
- int ecnt; /* error count */
- } CState;
- /*
- * Cache cell for zipfian_random call
- */
- typedef struct
- {
- /* cell keys */
- double s; /* s - parameter of zipfan_random function */
- int64 n; /* number of elements in range (max - min + 1) */
- double harmonicn; /* generalizedHarmonicNumber(n, s) */
- double alpha;
- double beta;
- double eta;
- uint64 last_used; /* last used logical time */
- } ZipfCell;
- /*
- * Zipf cache for zeta values
- */
- typedef struct
- {
- uint64 current; /* counter for LRU cache replacement algorithm */
- int nb_cells; /* number of filled cells */
- int overflowCount; /* number of cache overflows */
- ZipfCell cells[ZIPF_CACHE_SIZE];
- } ZipfCache;
- /*
- * Thread state
- */
- typedef struct
- {
- int tid; /* thread id */
- pthread_t thread; /* thread handle */
- CState *state; /* array of CState */
- int nstate; /* length of state[] */
- unsigned short random_state[3]; /* separate randomness for each thread */
- int64 throttle_trigger; /* previous/next throttling (us) */
- FILE *logfile; /* where to log, or NULL */
- ZipfCache zipf_cache; /* for thread-safe zipfian random number
- * generation */
- /* per thread collected stats */
- instr_time start_time; /* thread start time */
- instr_time conn_time;
- StatsData stats;
- int64 latency_late; /* executed but late transactions */
- } TState;
- #define INVALID_THREAD ((pthread_t) 0)
- /*
- * queries read from files
- */
- #define SQL_COMMAND 1
- #define META_COMMAND 2
- #define MAX_ARGS 10
- typedef enum MetaCommand
- {
- META_NONE, /* not a known meta-command */
- META_SET, /* \set */
- META_SETSHELL, /* \setshell */
- META_SHELL, /* \shell */
- META_SLEEP, /* \sleep */
- META_IF, /* \if */
- META_ELIF, /* \elif */
- META_ELSE, /* \else */
- META_ENDIF /* \endif */
- } MetaCommand;
- typedef enum QueryMode
- {
- QUERY_SIMPLE, /* simple query */
- QUERY_EXTENDED, /* extended query */
- QUERY_PREPARED, /* extended query with prepared statements */
- NUM_QUERYMODE
- } QueryMode;
- static QueryMode querymode = QUERY_SIMPLE;
- static const char *QUERYMODE[] = {"simple", "extended", "prepared"};
- typedef struct
- {
- char *line; /* text of command line */
- int command_num; /* unique index of this Command struct */
- int type; /* command type (SQL_COMMAND or META_COMMAND) */
- MetaCommand meta; /* meta command identifier, or META_NONE */
- int argc; /* number of command words */
- char *argv[MAX_ARGS]; /* command word list */
- PgBenchExpr *expr; /* parsed expression, if needed */
- SimpleStats stats; /* time spent in this command */
- } Command;
- typedef struct ParsedScript
- {
- const char *desc; /* script descriptor (eg, file name) */
- int weight; /* selection weight */
- Command **commands; /* NULL-terminated array of Commands */
- StatsData stats; /* total time spent in script */
- } ParsedScript;
- static ParsedScript sql_script[MAX_SCRIPTS]; /* SQL script files */
- static int num_scripts; /* number of scripts in sql_script[] */
- static int num_commands = 0; /* total number of Command structs */
- static int64 total_weight = 0;
- static int debug = 0; /* debug flag */
- /* Builtin test scripts */
- typedef struct BuiltinScript
- {
- const char *name; /* very short name for -b ... */
- const char *desc; /* short description */
- const char *script; /* actual pgbench script */
- } BuiltinScript;
- static const BuiltinScript builtin_script[] =
- {
- {
- "tpcb-like",
- "<builtin: TPC-B (sort of)>",
- "\\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"
- "UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;\n"
- "UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;\n"
- "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
- "END;\n"
- },
- {
- "simple-update",
- "<builtin: simple update>",
- "\\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"
- "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
- "END;\n"
- },
- {
- "select-only",
- "<builtin: select only>",
- "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
- "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
- }
- };
- /* Function prototypes */
- static void setNullValue(PgBenchValue *pv);
- static void setBoolValue(PgBenchValue *pv, bool bval);
- 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,
- StatsData *agg, bool skipped, double latency, double lag);
- static void processXactStats(TState *thread, CState *st, instr_time *now,
- bool skipped, StatsData *agg);
- static void pgbench_error(const char *fmt,...) pg_attribute_printf(1, 2);
- static void addScript(ParsedScript script);
- static void *threadRun(void *arg);
- static void setalarm(int seconds);
- static void finishCon(CState *st);
- /* callback functions for our flex lexer */
- static const PsqlScanCallbacks pgbench_callbacks = {
- NULL, /* don't need get_variable functionality */
- pgbench_error
- };
- static void
- usage(void)
- {
- printf("%s is a benchmarking tool for PostgreSQL.\n\n"
- "Usage:\n"
- " %s [OPTION]... [DBNAME]\n"
- "\nInitialization options:\n"
- " -i, --initialize invokes initialization mode\n"
- " -I, --init-steps=[dtgvpf]+ (default \"dtgvp\")\n"
- " run selected initialization steps\n"
- " -F, --fillfactor=NUM set fill factor\n"
- " -n, --no-vacuum do not run VACUUM during initialization\n"
- " -q, --quiet quiet logging (one message each 5 seconds)\n"
- " -s, --scale=NUM scaling factor\n"
- " --foreign-keys create foreign key constraints between tables\n"
- " --index-tablespace=TABLESPACE\n"
- " create indexes in the specified tablespace\n"
- " --tablespace=TABLESPACE create tables in the specified tablespace\n"
- " --unlogged-tables create tables as unlogged tables\n"
- "\nOptions to select what to run:\n"
- " -b, --builtin=NAME[@W] add builtin script NAME weighted at W (default: 1)\n"
- " (use \"-b list\" to list available scripts)\n"
- " -f, --file=FILENAME[@W] add script FILENAME weighted at W (default: 1)\n"
- " -N, --skip-some-updates skip updates of pgbench_tellers and pgbench_branches\n"
- " (same as \"-b simple-update\")\n"
- " -S, --select-only perform SELECT-only transactions\n"
- " (same as \"-b select-only\")\n"
- "\nBenchmarking options:\n"
- " -c, --client=NUM number of concurrent database clients (default: 1)\n"
- " -C, --connect establish new connection for each transaction\n"
- " -D, --define=VARNAME=VALUE\n"
- " define variable for use by custom script\n"
- " -j, --jobs=NUM number of threads (default: 1)\n"
- " -l, --log write transaction times to log file\n"
- " -L, --latency-limit=NUM count transactions lasting more than NUM ms as late\n"
- " -M, --protocol=simple|extended|prepared\n"
- " protocol for submitting queries (default: simple)\n"
- " -n, --no-vacuum do not run VACUUM before tests\n"
- " -P, --progress=NUM show thread progress report every NUM seconds\n"
- " -r, --report-latencies report average latency per command\n"
- " -R, --rate=NUM target rate in transactions per second\n"
- " -s, --scale=NUM report this scale factor in output\n"
- " -t, --transactions=NUM number of transactions each client runs (default: 10)\n"
- " -T, --time=NUM duration of benchmark test in seconds\n"
- " -v, --vacuum-all vacuum all four standard tables before tests\n"
- " --aggregate-interval=NUM aggregate data over NUM seconds\n"
- " --log-prefix=PREFIX prefix for transaction time log file\n"
- " (default: \"pgbench_log\")\n"
- " --progress-timestamp use Unix epoch timestamps for progress\n"
- " --random-seed=SEED set random seed (\"time\", \"rand\", integer)\n"
- " --sampling-rate=NUM fraction of transactions to log (e.g., 0.01 for 1%%)\n"
- "\nCommon options:\n"
- " -d, --debug print debugging output\n"
- " -h, --host=HOSTNAME database server host or socket directory\n"
- " -p, --port=PORT database server port number\n"
- " -U, --username=USERNAME connect as specified database user\n"
- " -V, --version output version information, then exit\n"
- " -?, --help show this help, then exit\n"
- "\n"
- "Report bugs to <pgsql-bugs@postgresql.org>.\n",
- 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
- *
- * This function is a modified version of scanint8() from
- * src/backend/utils/adt/int8.c.
- */
- int64
- strtoint64(const char *str)
- {
- const char *ptr = str;
- int64 result = 0;
- int sign = 1;
- /*
- * Do our own scan, rather than relying on sscanf which might be broken
- * for long long.
- */
- /* skip leading spaces */
- while (*ptr && isspace((unsigned char) *ptr))
- ptr++;
- /* handle sign */
- if (*ptr == '-')
- {
- ptr++;
- /*
- * Do an explicit check for INT64_MIN. Ugly though this is, it's
- * cleaner than trying to get the loop below to handle it portably.
- */
- if (strncmp(ptr, "9223372036854775808", 19) == 0)
- {
- result = PG_INT64_MIN;
- ptr += 19;
- goto gotdigits;
- }
- sign = -1;
- }
- else if (*ptr == '+')
- ptr++;
- /* require at least one digit */
- if (!isdigit((unsigned char) *ptr))
- fprintf(stderr, "invalid input syntax for integer: \"%s\"\n", str);
- /* process digits */
- while (*ptr && isdigit((unsigned char) *ptr))
- {
- int64 tmp = result * 10 + (*ptr++ - '0');
- if ((tmp / 10) != result) /* overflow? */
- fprintf(stderr, "value \"%s\" is out of range for type bigint\n", str);
- result = tmp;
- }
- gotdigits:
- /* allow trailing whitespace, but not other trailing chars */
- while (*ptr != '\0' && isspace((unsigned char) *ptr))
- ptr++;
- if (*ptr != '\0')
- fprintf(stderr, "invalid input syntax for integer: \"%s\"\n", str);
- return ((sign < 0) ? -result : result);
- }
- /* random number generator: uniform distribution from min to max inclusive */
- static int64
- getrand(TState *thread, int64 min, int64 max)
- {
- /*
- * Odd coding is so that min and max have approximately the same chance of
- * being selected as do numbers between them.
- *
- * pg_erand48() is thread-safe and concurrent, which is why we use it
- * rather than random(), which in glibc is non-reentrant, and therefore
- * protected by a mutex, and therefore a bottleneck on machines with many
- * CPUs.
- */
- return min + (int64) ((max - min + 1) * pg_erand48(thread->random_state));
- }
- /*
- * random number generator: exponential distribution from min to max inclusive.
- * the parameter is so that the density of probability for the last cut-off max
- * value is exp(-parameter).
- */
- static int64
- getExponentialRand(TState *thread, int64 min, int64 max, double parameter)
- {
- double cut,
- 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] */
- uniform = 1.0 - pg_erand48(thread->random_state);
- /*
- * inner expression in (cut, 1] (if parameter > 0), rand in [0, 1)
- */
- Assert((1.0 - cut) != 0.0);
- rand = -log(cut + (1.0 - cut) * uniform) / parameter;
- /* return int64 random number within between min and max */
- return min + (int64) ((max - min + 1) * rand);
- }
- /* random number generator: gaussian distribution from min to max inclusive */
- static int64
- 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
- *
- * This loop is executed until the number is in the expected range.
- *
- * As the minimum parameter is 2.0, the probability of looping is low:
- * sqrt(-2 ln(r)) <= 2 => r >= e^{-2} ~ 0.135, then when taking the
- * average sinus multiplier as 2/pi, we have a 8.6% looping probability in
- * the worst case. For a parameter value of 5.0, the looping probability
- * is about e^{-5} * 2 / pi ~ 0.43%.
- */
- do
- {
- /*
- * pg_erand48 generates [0,1), but for the basic version of the
- * Box-Muller transform the two uniformly distributed random numbers
- * are expected in (0, 1] (see
- * http://en.wikipedia.org/wiki/Box_muller)
- */
- double rand1 = 1.0 - pg_erand48(thread->random_state);
- double rand2 = 1.0 - pg_erand48(thread->random_state);
- /* Box-Muller basic form transform */
- double var_sqrt = sqrt(-2.0 * log(rand1));
- stdev = var_sqrt * sin(2.0 * M_PI * rand2);
- /*
- * we may try with cos, but there may be a bias induced if the
- * previous value fails the test. To be on the safe side, let us try
- * over.
- */
- }
- while (stdev < -parameter || stdev >= parameter);
- /* stdev is in [-parameter, parameter), normalization to [0,1) */
- rand = (stdev + parameter) / (parameter * 2.0);
- /* return int64 random number within between min and max */
- return min + (int64) ((max - min + 1) * rand);
- }
- /*
- * random number generator: generate a value, such that the series of values
- * will approximate a Poisson distribution centered on the given value.
- */
- static int64
- getPoissonRand(TState *thread, int64 center)
- {
- /*
- * Use inverse transform sampling to generate a value > 0, such that the
- * expected (i.e. average) value is the given argument.
- */
- double uniform;
- /* erand in [0, 1), uniform in (0, 1] */
- uniform = 1.0 - pg_erand48(thread->random_state);
- return (int64) (-log(uniform) * ((double) center) + 0.5);
- }
- /* helper function for getZipfianRand */
- static double
- generalizedHarmonicNumber(int64 n, double s)
- {
- int i;
- double ans = 0.0;
- for (i = n; i > 1; i--)
- ans += pow(i, -s);
- return ans + 1.0;
- }
- /* set harmonicn and other parameters to cache cell */
- static void
- zipfSetCacheCell(ZipfCell *cell, int64 n, double s)
- {
- double harmonic2;
- cell->n = n;
- cell->s = s;
- harmonic2 = generalizedHarmonicNumber(2, s);
- cell->harmonicn = generalizedHarmonicNumber(n, s);
- cell->alpha = 1.0 / (1.0 - s);
- cell->beta = pow(0.5, s);
- cell->eta = (1.0 - pow(2.0 / n, 1.0 - s)) / (1.0 - harmonic2 / cell->harmonicn);
- }
- /*
- * search for cache cell with keys (n, s)
- * and create new cell if it does not exist
- */
- static ZipfCell *
- zipfFindOrCreateCacheCell(ZipfCache *cache, int64 n, double s)
- {
- int i,
- least_recently_used = 0;
- ZipfCell *cell;
- /* search cached cell for given parameters */
- for (i = 0; i < cache->nb_cells; i++)
- {
- cell = &cache->cells[i];
- if (cell->n == n && cell->s == s)
- return &cache->cells[i];
- if (cell->last_used < cache->cells[least_recently_used].last_used)
- least_recently_used = i;
- }
- /* create new one if it does not exist */
- if (cache->nb_cells < ZIPF_CACHE_SIZE)
- i = cache->nb_cells++;
- else
- {
- /* replace LRU cell if cache is full */
- i = least_recently_used;
- cache->overflowCount++;
- }
- zipfSetCacheCell(&cache->cells[i], n, s);
- cache->cells[i].last_used = cache->current++;
- return &cache->cells[i];
- }
- /*
- * Computing zipfian using rejection method, based on
- * "Non-Uniform Random Variate Generation",
- * Luc Devroye, p. 550-551, Springer 1986.
- */
- static int64
- computeIterativeZipfian(TState *thread, int64 n, double s)
- {
- double b = pow(2.0, s - 1.0);
- double x,
- t,
- u,
- v;
- while (true)
- {
- /* random variates */
- u = pg_erand48(thread->random_state);
- v = pg_erand48(thread->random_state);
- x = floor(pow(u, -1.0 / (s - 1.0)));
- t = pow(1.0 + 1.0 / x, s - 1.0);
- /* reject if too large or out of bound */
- if (v * x * (t - 1.0) / (b - 1.0) <= t / b && x <= n)
- break;
- }
- return (int64) x;
- }
- /*
- * Computing zipfian using harmonic numbers, based on algorithm described in
- * "Quickly Generating Billion-Record Synthetic Databases",
- * Jim Gray et al, SIGMOD 1994
- */
- static int64
- computeHarmonicZipfian(TState *thread, int64 n, double s)
- {
- ZipfCell *cell = zipfFindOrCreateCacheCell(&thread->zipf_cache, n, s);
- double uniform = pg_erand48(thread->random_state);
- double uz = uniform * cell->harmonicn;
- if (uz < 1.0)
- return 1;
- if (uz < 1.0 + cell->beta)
- return 2;
- return 1 + (int64) (cell->n * pow(cell->eta * uniform - cell->eta + 1.0, cell->alpha));
- }
- /* random number generator: zipfian distribution from min to max inclusive */
- static int64
- getZipfianRand(TState *thread, int64 min, int64 max, double s)
- {
- int64 n = max - min + 1;
- /* abort if parameter is invalid */
- Assert(s > 0.0 && s != 1.0 && s <= MAX_ZIPFIAN_PARAM);
- return min - 1 + ((s > 1)
- ? computeIterativeZipfian(thread, n, s)
- : computeHarmonicZipfian(thread, n, s));
- }
- /*
- * FNV-1a hash function
- */
- static int64
- getHashFnv1a(int64 val, uint64 seed)
- {
- int64 result;
- int i;
- result = FNV_OFFSET_BASIS ^ seed;
- for (i = 0; i < 8; ++i)
- {
- int32 octet = val & 0xff;
- val = val >> 8;
- result = result ^ octet;
- result = result * FNV_PRIME;
- }
- return result;
- }
- /*
- * Murmur2 hash function
- *
- * Based on original work of Austin Appleby
- * https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp
- */
- static int64
- getHashMurmur2(int64 val, uint64 seed)
- {
- uint64 result = seed ^ MM2_MUL_TIMES_8; /* sizeof(int64) */
- uint64 k = (uint64) val;
- k *= MM2_MUL;
- k ^= k >> MM2_ROT;
- k *= MM2_MUL;
- result ^= k;
- result *= MM2_MUL;
- result ^= result >> MM2_ROT;
- result *= MM2_MUL;
- result ^= result >> MM2_ROT;
- return (int64) result;
- }
- /*
- * Initialize the given SimpleStats struct to all zeroes
- */
- static void
- initSimpleStats(SimpleStats *ss)
- {
- memset(ss, 0, sizeof(SimpleStats));
- }
- /*
- * Accumulate one value into a SimpleStats struct.
- */
- static void
- addToSimpleStats(SimpleStats *ss, double val)
- {
- if (ss->count == 0 || val < ss->min)
- ss->min = val;
- if (ss->count == 0 || val > ss->max)
- ss->max = val;
- ss->count++;
- ss->sum += val;
- ss->sum2 += val * val;
- }
- /*
- * Merge two SimpleStats objects
- */
- static void
- mergeSimpleStats(SimpleStats *acc, SimpleStats *ss)
- {
- if (acc->count == 0 || ss->min < acc->min)
- acc->min = ss->min;
- if (acc->count == 0 || ss->max > acc->max)
- acc->max = ss->max;
- acc->count += ss->count;
- acc->sum += ss->sum;
- acc->sum2 += ss->sum2;
- }
- /*
- * Initialize a StatsData struct to mostly zeroes, with its start time set to
- * the given value.
- */
- static void
- initStats(StatsData *sd, time_t start_time)
- {
- sd->start_time = start_time;
- sd->cnt = 0;
- sd->skipped = 0;
- initSimpleStats(&sd->latency);
- initSimpleStats(&sd->lag);
- }
- /*
- * Accumulate one additional item into the given stats object.
- */
- static void
- accumStats(StatsData *stats, bool skipped, double lat, double lag)
- {
- stats->cnt++;
- if (skipped)
- {
- /* no latency to record on skipped transactions */
- stats->skipped++;
- }
- else
- {
- addToSimpleStats(&stats->latency, lat);
- /* and possibly the same for schedule lag */
- if (throttle_delay)
- addToSimpleStats(&stats->lag, lag);
- }
- }
- /* call PQexec() and exit() on failure */
- static void
- executeStatement(PGconn *con, const char *sql)
- {
- PGresult *res;
- res = PQexec(con, sql);
- if (PQresultStatus(res) != PGRES_COMMAND_OK)
- {
- fprintf(stderr, "%s", PQerrorMessage(con));
- exit(1);
- }
- PQclear(res);
- }
- /* call PQexec() and complain, but without exiting, on failure */
- static void
- tryExecuteStatement(PGconn *con, const char *sql)
- {
- PGresult *res;
- res = PQexec(con, sql);
- if (PQresultStatus(res) != PGRES_COMMAND_OK)
- {
- fprintf(stderr, "%s", PQerrorMessage(con));
- fprintf(stderr, "(ignoring this error and continuing anyway)\n");
- }
- PQclear(res);
- }
- /* set up a connection to the backend */
- static PGconn *
- doConnect(void)
- {
- PGconn *conn;
- bool new_pass;
- static bool have_password = false;
- static char password[100];
- /*
- * Start the connection. Loop until we have a password if requested by
- * backend.
- */
- do
- {
- #define PARAMS_ARRAY_SIZE 7
- const char *keywords[PARAMS_ARRAY_SIZE];
- const char *values[PARAMS_ARRAY_SIZE];
- keywords[0] = "host";
- values[0] = pghost;
- keywords[1] = "port";
- values[1] = pgport;
- keywords[2] = "user";
- values[2] = login;
- keywords[3] = "password";
- values[3] = have_password ? password : NULL;
- keywords[4] = "dbname";
- values[4] = dbName;
- keywords[5] = "fallback_application_name";
- values[5] = progname;
- keywords[6] = NULL;
- values[6] = NULL;
- new_pass = false;
- conn = PQconnectdbParams(keywords, values, true);
- if (!conn)
- {
- fprintf(stderr, "connection to database \"%s\" failed\n",
- dbName);
- return NULL;
- }
- if (PQstatus(conn) == CONNECTION_BAD &&
- PQconnectionNeedsPassword(conn) &&
- !have_password)
- {
- PQfinish(conn);
- simple_prompt("Password: ", password, sizeof(password), false);
- have_password = true;
- new_pass = true;
- }
- } while (new_pass);
- /* check to see that the backend connection was successfully made */
- if (PQstatus(conn) == CONNECTION_BAD)
- {
- fprintf(stderr, "connection to database \"%s\" failed:\n%s",
- dbName, PQerrorMessage(conn));
- PQfinish(conn);
- return NULL;
- }
- return conn;
- }
- /* throw away response from backend */
- static void
- discard_response(CState *state)
- {
- PGresult *res;
- do
- {
- res = PQgetResult(state->con);
- if (res)
- PQclear(res);
- } 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->svalue)
- return var->svalue; /* we have it in string form */
- /* We need to produce a string equivalent of the value */
- Assert(var->value.type != PGBT_NO_VALUE);
- if (var->value.type == PGBT_NULL)
- snprintf(stringform, sizeof(stringform), "NULL");
- else if (var->value.type == PGBT_BOOLEAN)
- snprintf(stringform, sizeof(stringform),
- "%s", var->value.u.bval ? "true" : "false");
- else if (var->value.type == PGBT_INT)
- snprintf(stringform, sizeof(stringform),
- INT64_FORMAT, var->value.u.ival);
- else if (var->value.type == PGBT_DOUBLE)
- snprintf(stringform, sizeof(stringform),
- "%.*g", DBL_DIG, var->value.u.dval);
- else /* internal error, unexpected type */
- Assert(0);
- var->svalue = pg_strdup(stringform);
- return var->svalue;
- }
- /* Try to convert variable to a value; return false on failure */
- static bool
- makeVariableValue(Variable *var)
- {
- size_t slen;
- if (var->value.type != PGBT_NO_VALUE)
- return true; /* no work */
- slen = strlen(var->svalue);
- if (slen == 0)
- /* what should it do on ""? */
- return false;
- if (pg_strcasecmp(var->svalue, "null") == 0)
- {
- setNullValue(&var->value);
- }
- /*
- * accept prefixes such as y, ye, n, no... but not for "o". 0/1 are
- * recognized later as an int, which is converted to bool if needed.
- */
- else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
- pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
- pg_strcasecmp(var->svalue, "on") == 0)
- {
- setBoolValue(&var->value, true);
- }
- else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
- pg_strncasecmp(var->svalue, "no", slen) == 0 ||
- pg_strcasecmp(var->svalue, "off") == 0 ||
- pg_strcasecmp(var->svalue, "of") == 0)
- {
- setBoolValue(&var->value, false);
- }
- else if (is_an_int(var->svalue))
- {
- setIntValue(&var->value, strtoint64(var->svalue));
- }
- else /* type should be double */
- {
- double dv;
- char xs;
- if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
- {
- fprintf(stderr,
- "malformed variable \"%s\" value: \"%s\"\n",
- var->name, var->svalue);
- return false;
- }
- setDoubleValue(&var->value, dv);
- }
- return true;
- }
- /*
- * Check whether a variable's name is allowed.
- *
- * We allow any non-ASCII character, as well as ASCII letters, digits, and
- * underscore.
- *
- * Keep this in sync with the definitions of variable name characters in
- * "src/fe_utils/psqlscan.l", "src/bin/psql/psqlscanslash.l" and
- * "src/bin/pgbench/exprscan.l". Also see parseVariable(), below.
- *
- * Note: this static function is copied from "src/bin/psql/variables.c"
- */
- static bool
- valid_variable_name(const char *name)
- {
- const unsigned char *ptr = (const unsigned char *) name;
- /* Mustn't be zero-length */
- if (*ptr == '\0')
- return false;
- while (*ptr)
- {
- if (IS_HIGHBIT_SET(*ptr) ||
- strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
- "_0123456789", *ptr) != NULL)
- ptr++;
- else
- return false;
- }
- return true;
- }
- /*
- * 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;
- /*
- * Check for the name only when declaring a new variable to avoid
- * overhead.
- */
- if (!valid_variable_name(name))
- {
- 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));
- else
- newvars = (Variable *) pg_malloc(sizeof(Variable));
- st->variables = newvars;
- var = &newvars[st->nvariables];
- var->name = pg_strdup(name);
- var->svalue = 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->svalue)
- free(var->svalue);
- var->svalue = val;
- var->value.type = PGBT_NO_VALUE;
- return true;
- }
- /* Assign a value to a variable, creating it if need be */
- /* Returns false on failure (bad name) */
- static bool
- putVariableValue(CState *st, const char *context, char *name,
- const PgBenchValue *value)
- {
- Variable *var;
- var = lookupCreateVariable(st, context, name);
- if (!var)
- return false;
- if (var->svalue)
- free(var->svalue);
- var->svalue = NULL;
- var->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 putVariableValue(st, context, name, &val);
- }
- /*
- * Parse a possible variable reference (:varname).
- *
- * "sql" points at a colon. If what follows it looks like a valid
- * variable name, return a malloc'd string containing the variable name,
- * and set *eaten to the number of characters consumed.
- * Otherwise, return NULL.
- */
- static char *
- parseVariable(const char *sql, int *eaten)
- {
- int i = 0;
- char *name;
- do
- {
- i++;
- } while (IS_HIGHBIT_SET(sql[i]) ||
- strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
- "_0123456789", sql[i]) != NULL);
- if (i == 1)
- return NULL; /* no valid variable name chars */
- name = pg_malloc(i);
- memcpy(name, &sql[1], i - 1);
- name[i - 1] = '\0';
- *eaten = i;
- return name;
- }
- static char *
- replaceVariable(char **sql, char *param, int len, char *value)
- {
- int valueln = strlen(value);
- if (valueln > len)
- {
- size_t offset = param - *sql;
- *sql = pg_realloc(*sql, strlen(*sql) - len + valueln + 1);
- param = *sql + offset;
- }
- if (valueln != len)
- memmove(param + valueln, param + len, strlen(param + len) + 1);
- memcpy(param, value, valueln);
- return param + valueln;
- }
- static char *
- assignVariables(CState *st, char *sql)
- {
- char *p,
- *name,
- *val;
- p = sql;
- while ((p = strchr(p, ':')) != NULL)
- {
- int eaten;
- name = parseVariable(p, &eaten);
- if (name == NULL)
- {
- while (*p == ':')
- {
- p++;
- }
- continue;
- }
- val = getVariable(st, name);
- free(name);
- if (val == NULL)
- {
- p++;
- continue;
- }
- p = replaceVariable(&sql, p, eaten, val);
- }
- return sql;
- }
- static void
- getQueryParams(CState *st, const Command *command, const char **params)
- {
- int i;
- for (i = 0; i < command->argc - 1; i++)
- params[i] = getVariable(st, command->argv[i + 1]);
- }
- static char *
- valueTypeName(PgBenchValue *pval)
- {
- if (pval->type == PGBT_NO_VALUE)
- return "none";
- else if (pval->type == PGBT_NULL)
- return "null";
- else if (pval->type == PGBT_INT)
- return "int";
- else if (pval->type == PGBT_DOUBLE)
- return "double";
- else if (pval->type == PGBT_BOOLEAN)
- return "boolean";
- else
- {
- /* internal error, should never get there */
- Assert(false);
- return NULL;
- }
- }
- /* get a value as a boolean, or tell if there is a problem */
- static bool
- coerceToBool(PgBenchValue *pval, bool *bval)
- {
- if (pval->type == PGBT_BOOLEAN)
- {
- *bval = pval->u.bval;
- return true;
- }
- else /* NULL, INT or DOUBLE */
- {
- fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
- *bval = false; /* suppress uninitialized-variable warnings */
- return false;
- }
- }
- /*
- * Return true or false from an expression for conditional purposes.
- * Non zero numerical values are true, zero and NULL are false.
- */
- static bool
- valueTruth(PgBenchValue *pval)
- {
- switch (pval->type)
- {
- case PGBT_NULL:
- return false;
- case PGBT_BOOLEAN:
- return pval->u.bval;
- case PGBT_INT:
- return pval->u.ival != 0;
- case PGBT_DOUBLE:
- return pval->u.dval != 0.0;
- default:
- /* internal error, unexpected type */
- Assert(0);
- return false;
- }
- }
- /* 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 if (pval->type == PGBT_DOUBLE)
- {
- double dval = pval->u.dval;
- if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
- {
- fprintf(stderr, "double to int overflow for %f\n", dval);
- return false;
- }
- *ival = (int64) dval;
- return true;
- }
- else /* BOOLEAN or NULL */
- {
- fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
- return false;
- }
- }
- /* 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 if (pval->type == PGBT_INT)
- {
- *dval = (double) pval->u.ival;
- return true;
- }
- else /* BOOLEAN or NULL */
- {
- fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
- return false;
- }
- }
- /* assign a null value */
- static void
- setNullValue(PgBenchValue *pv)
- {
- pv->type = PGBT_NULL;
- pv->u.ival = 0;
- }
- /* assign a boolean value */
- static void
- setBoolValue(PgBenchValue *pv, bool bval)
- {
- pv->type = PGBT_BOOLEAN;
- pv->u.bval = bval;
- }
- /* 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;
- }
- static bool
- isLazyFunc(PgBenchFunction func)
- {
- return func == PGBENCH_AND || func == PGBENCH_OR || func == PGBENCH_CASE;
- }
- /* lazy evaluation of some functions */
- static bool
- evalLazyFunc(TState *thread, CState *st,
- PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
- {
- PgBenchValue a1,
- a2;
- bool ba1,
- ba2;
- Assert(isLazyFunc(func) && args != NULL && args->next != NULL);
- /* args points to first condition */
- if (!evaluateExpr(thread, st, args->expr, &a1))
- return false;
- /* second condition for AND/OR and corresponding branch for CASE */
- args = args->next;
- switch (func)
- {
- case PGBENCH_AND:
- if (a1.type == PGBT_NULL)
- {
- setNullValue(retval);
- return true;
- }
- if (!coerceToBool(&a1, &ba1))
- return false;
- if (!ba1)
- {
- setBoolValue(retval, false);
- return true;
- }
- if (!evaluateExpr(thread, st, args->expr, &a2))
- return false;
- if (a2.type == PGBT_NULL)
- {
- setNullValue(retval);
- return true;
- }
- else if (!coerceToBool(&a2, &ba2))
- return false;
- else
- {
- setBoolValue(retval, ba2);
- return true;
- }
- return true;
- case PGBENCH_OR:
- if (a1.type == PGBT_NULL)
- {
- setNullValue(retval);
- return true;
- }
- if (!coerceToBool(&a1, &ba1))
- return false;
- if (ba1)
- {
- setBoolValue(retval, true);
- return true;
- }
- if (!evaluateExpr(thread, st, args->expr, &a2))
- return false;
- if (a2.type == PGBT_NULL)
- {
- setNullValue(retval);
- return true;
- }
- else if (!coerceToBool(&a2, &ba2))
- return false;
- else
- {
- setBoolValue(retval, ba2);
- return true;
- }
- case PGBENCH_CASE:
- /* when true, execute branch */
- if (valueTruth(&a1))
- return evaluateExpr(thread, st, args->expr, retval);
- /* now args contains next condition or final else expression */
- args = args->next;
- /* final else case? */
- if (args->next == NULL)
- return evaluateExpr(thread, st, args->expr, retval);
- /* no, another when, proceed */
- return evalLazyFunc(thread, st, PGBENCH_CASE, args, retval);
- default:
- /* internal error, cannot get here */
- Assert(0);
- break;
- }
- return false;
- }
- /* maximum number of function arguments */
- #define MAX_FARGS 16
- /*
- * Recursive evaluation of standard functions,
- * which do not require lazy evaluation.
- */
- static bool
- evalStandardFunc(TState *thread, CState *st,
- PgBenchFunction func, PgBenchExprLink *args,
- PgBenchValue *retval)
- {
- /* evaluate all function arguments */
- int nargs = 0;
- PgBenchValue vargs[MAX_FARGS];
- PgBenchExprLink *l = args;
- bool has_null = false;
- for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
- {
- if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
- return false;
- has_null |= vargs[nargs].type == PGBT_NULL;
- }
- if (l != NULL)
- {
- fprintf(stderr,
- "too many function arguments, maximum is %d\n", MAX_FARGS);
- return false;
- }
- /* NULL arguments */
- if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG)
- {
- setNullValue(retval);
- return true;
- }
- /* then evaluate function */
- switch (func)
- {
- /* overloaded operators */
- case PGBENCH_ADD:
- case PGBENCH_SUB:
- case PGBENCH_MUL:
- case PGBENCH_DIV:
- case PGBENCH_MOD:
- case PGBENCH_EQ:
- case PGBENCH_NE:
- case PGBENCH_LE:
- case PGBENCH_LT:
- {
- PgBenchValue *lval = &vargs[0],
- *rval = &vargs[1];
- Assert(nargs == 2);
- /* overloaded type management, double if some double */
- if ((lval->type == PGBT_DOUBLE ||
- rval->type == PGBT_DOUBLE) && func != PGBENCH_MOD)
- {
- double ld,
- rd;
- if (!coerceToDouble(lval, &ld) ||
- !coerceToDouble(rval, &rd))
- return false;
- switch (func)
- {
- case PGBENCH_ADD:
- setDoubleValue(retval, ld + rd);
- return true;
- 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;
- case PGBENCH_EQ:
- setBoolValue(retval, ld == rd);
- return true;
- case PGBENCH_NE:
- setBoolValue(retval, ld != rd);
- return true;
- case PGBENCH_LE:
- setBoolValue(retval, ld <= rd);
- return true;
- case PGBENCH_LT:
- setBoolValue(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_EQ:
- setBoolValue(retval, li == ri);
- return true;
- case PGBENCH_NE:
- setBoolValue(retval, li != ri);
- return true;
- case PGBENCH_LE:
- setBoolValue(retval, li <= ri);
- return true;
- case PGBENCH_LT:
- setBoolValue(retval, li…
Large files files are truncated, but you can click here to view the full file