Hi
Currently pgbench allows only standard database initialisation to be
offloaded to the server side. It would be nice to do the same for any
initialisation or testing code.
Attached are two patches which extract the statistical random
distribution generators, domain permutations, and 64-bit hashing
routines from the current monolith contrib/pgbench/pgbench.c source
file and make them also available as a pgbench server extension so
that more workloads can run the same way in pgbench and the server.
The code is almost entirely generated by AI harness. However, since
this work involves well-defined refactoring and wrapping C code to be
callable from SQL, the code quality should not be affected. I found
nothing obvious to change during the review.
I have also attached a proposal for Refactoring pgbench for a Modular
Architecture to make both maintenance and future additions easier.
Since this is again mainly shuffling existing code around I expect
this to be a relatively low human effort endeavour but I would still
like to get some feedback before taking it on. The end of that
document hints at some possible enhancements.
----------------
Following is the rep[ort of the changes.
## Executive Summary
This patch set extracted the statistical distribution, pseudorandom
permutation, and 64-bit hashing algorithms from the pgbench frontend
client (src/bin/pgbench/pgbench.c) into PostgreSQL's shared common library
(src/common/pgbench_funcs.c and src/include/common/pgbench_funcs.h).
## The implementation is structured across two independent Git commits:
Commit 1 : Library extraction into src/common/ and
reintegration into src/bin/pgbench/pgbench.c.
Commit 2 : Addition of the contrib/pgbench server extension exposing
all functions in SCHEMA pgbench, with complete SGML documentation.
┌─────────────────────────────────────────┐
│ src/include/common/pgbench_funcs.h │
│ src/common/pgbench_funcs.c │
└────────────────────┬────────────────────┘
│
┌─────────────────┴─────────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ src/bin/pgbench │ │ contrib/pgbench │
│ (Frontend Client) │ │ (Server Extension) │
│ │ │ │
│ Links: libpgcommon.a │ │ Links: pgbench_funcs.o │
│ Calls: │ │ Exposes: │
│ - pgbench_random() │ │ - pgbench.random() │
│ - pgbench_random_*() │ │ - pgbench.random_*() │
│ - pgbench_hash_*() │ │ - pgbench.hash_*() │
│ - pgbench_permute() │ │ - pgbench.permute() │
└───────────────────────────┘ └───────────────────────────┘
### Commit 1: Library Extraction & Frontend Reintegration
- Commit ID: 31e8e96c92e
- Message: Extract pgbench low-level functions into src/common
Key Changes
1. Header Definition (src/include/common/pgbench_funcs.h):
- Distribution parameter bounds: PGBENCH_MIN_GAUSSIAN_PARAM (2.0),
PGBENCH_MIN_ZIPFIAN_PARAM (1.001), PGBENCH_MAX_ZIPFIAN_PARAM (1000.0).
- Hashing constants: PGBENCH_FNV_PRIME, PGBENCH_FNV_OFFSET_BASIS,
PGBENCH_MM2_MUL, PGBENCH_MM2_MUL_TIMES_8, PGBENCH_MM2_ROT.
- Public function prototypes taking explicit pg_prng_state *state pointers
and primitive scalar types.
2. Common Implementation (src/common/pgbench_funcs.c):
- Implemented pure, zero-dependency routines compiled for both frontend
(-DFRONTEND) and backend without server header entanglements.
- Extracted routines:
- pgbench_random(pg_prng_state *state, int64 min, int64 max)
- pgbench_random_gaussian(pg_prng_state *state, int64 min, int64 max,
double parameter)
- pgbench_random_exponential(pg_prng_state *state, int64 min, int64
max, double parameter)
- pgbench_random_zipfian(pg_prng_state *state, int64 min, int64 max,
double s) & computeIterativeZipfian()
- pgbench_random_poisson(pg_prng_state *state, double center)
- pgbench_hash_fnv1a(int64 val, uint64 seed)
- pgbench_hash_murmur2(int64 val, uint64 seed)
- pgbench_permute(int64 val, int64 isize, int64 seed)
3. Build System Registration:
- src/common/Makefile: Added pgbench_funcs.o to OBJS_COMMON.
- src/common/meson.build: Added 'pgbench_funcs.c' to common_sources.
4. Frontend Reintegration (src/bin/pgbench/pgbench.c):
- Included common/pgbench_funcs.h.
- Removed duplicate static implementations (getrand, getGaussianRand,
getExponentialRand, getZipfianRand, getPoissonRand, getHashMurmur2,
getHashFnv1a, permute).
- Replaced call sites in evalStandardFunc(), chooseScript(), and
connection throttling loops with pgbench_* functions.
### Commit 2: contrib/pgbench Server Extension & Documentation
- Commit ID: 53c905816e2
- Message: Add contrib/pgbench extension exposing benchmark functions in
SCHEMA pgbench
Key Changes
1. Extension Control & Schema (contrib/pgbench/pgbench.control):
# pgbench extension
comment = 'pgbench distribution, permutation, and hashing functions'
default_version = '1.0'
module_pathname = '$libdir/pgbench'
relocatable = false
schema = 'pgbench'
2. SQL Interface Definition (contrib/pgbench/pgbench--1.0.sql):
- Bound to SCHEMA pgbench upon CREATE EXTENSION pgbench;.
- Complete function catalog:
SQL Function Signature
pgbench.setseed(seed double precision)
RETURNS void VOLATILE PARALLEL UNSAFE
"Seed PRNG with float in [-1.0, 1.0]"
pgbench.setseed(seed bigint)
RETURNS void VOLATILE PARALLEL UNSAFE
"Seed PRNG with 64-bit integer"
pgbench.random(min bigint, max bigint)
RETURNS bigint VOLATILE PARALLEL SAFE
"Uniform random integer in [min, max]"
pgbench.random_gaussian(min bigint, max bigint,
parameter double precision)
RETURNS bigint VOLATILE PARALLEL SAFE
"Gaussian random in [min, max], param >= 2.0"
pgbench.random_exponential(min bigint, max bigint,
parameter double precision)
RETURNS bigint VOLATILE PARALLEL SAFE
"Exponential random in [min, max], param > 0.0"
pgbench.random_zipfian(min bigint, max bigint,
parameter double precision)
RETURNS bigint VOLATILE PARALLEL SAFE
"Zipfian random in [min, max], param in [1.001, 1000.0]"
pgbench.random_poisson(center double precision)
RETURNS bigint VOLATILE PARALLEL SAFE
"Poisson random, center > 0.0"
pgbench.hash_murmur2(val bigint, seed bigint DEFAULT 0)
RETURNS bigint IMMUTABLE PARALLEL SAFE
"64-bit Austin Appleby MurmurHash2"
pgbench.hash_fnv1a(val bigint, seed bigint DEFAULT 0)
RETURNS bigint IMMUTABLE PARALLEL SAFE
"64-bit Fowler–Noll–Vo 1a hash"
pgbench.hash(val bigint, seed bigint DEFAULT 0)
RETURNS bigint IMMUTABLE PARALLEL SAFE
"Alias for pgbench.hash_murmur2"
pgbench.permute(val bigint, size bigint, seed bigint DEFAULT 0)
RETURNS bigint IMMUTABLE PARALLEL SAFE
"Bijective pseudorandom permutation of [0, size)"
3. Backend C Module (contrib/pgbench/pgbench.c):
- Per-backend PRNG state initialized via strong random
(pg_prng_strong_seed) or timestamp/PID fallback, customizable via
pgbench.setseed().
- Robust argument verification: range checks, numeric overflow detection
via common/int.h (pg_sub_s64_overflow, pg_add_s64_overflow), and
parameter domain validation throwing standard PostgreSQL ereport(ERROR,
...).
4. Documentation:
- Created doc/src/sgml/pgbench-ext.sgml documenting all functions,
argument limits, behaviors, and SQL examples.
- Registered in doc/src/sgml/filelist.sgml and doc/src/sgml/contrib.sgml
(including addition to trusted extensions list).
- Verified with SGML syntax validation (make -C doc/src/sgml check).
5. Build System & Regression Suites:
- contrib/pgbench/Makefile & contrib/pgbench/meson.build.
- Registered in parent contrib/Makefile and contrib/meson.build.
- Comprehensive regression test suite in contrib/pgbench/sql/pgbench.sql
and expected output in contrib/pgbench/expected/pgbench.out.
### Test Verification Results
1. pgbench Client TAP Tests
make -C src/bin/pgbench check
# +++ tap check in src/bin/pgbench +++
t/001_pgbench_with_server.pl .. ok
t/002_pgbench_no_server.pl .... ok
All tests successful.
Files=2, Tests=681, 5 wallclock secs
Result: PASS
2. contrib/pgbench Extension Regression Tests
make -C contrib/pgbench check
# +++ regress check in contrib/pgbench +++
# initializing database system by copying initdb template
# using temp instance on port 52544 with PID 2123253
ok 1 - pgbench 17 ms
1..1
# All 1 tests passed.
3. SGML Documentation Validation
make -C doc/src/sgml check
/usr/bin/xmllint --nonet --path . --path . --noout --valid postgres.sgml
Result: PASS (0 errors, 0 warnings)
### File Inventory & Git Diffs
Commit 1 Changes
src/bin/pgbench/pgbench.c | 342 ++--------------------------------
src/common/Makefile | 1 +
src/common/meson.build | 1 +
src/common/pgbench_funcs.c | 320 +++++++++++++++++++++++++++++++++
src/include/common/pgbench_funcs.h | 48 ++++++
5 files changed, 386 insertions(+), 326 deletions(-)
Commit 2 Changes
contrib/Makefile | 1 +
contrib/meson.build | 1 +
contrib/pgbench/Makefile | 30 +++++
contrib/pgbench/expected/pgbench.out | 153 ++++++++++++++++++++++
contrib/pgbench/meson.build | 36 ++++++
contrib/pgbench/pgbench--1.0.sql | 69 ++++++++++
contrib/pgbench/pgbench.c | 228 ++++++++++++++++++++++++++++++++
contrib/pgbench/pgbench.control | 6 +
contrib/pgbench/sql/pgbench.sql | 52 ++++++++
doc/src/sgml/contrib.sgml | 2 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/pgbench-ext.sgml | 240 ++++++++++++++++++++++++++++++++++
12 files changed, 819 insertions(+)
From 53c905816e20947d8ff0cd0c51e8b5690834255b Mon Sep 17 00:00:00 2001
From: Hannu Krosing <[email protected]>
Date: Tue, 25 Aug 2026 22:13:19 +0000
Subject: [PATCH v2 2/2] Add contrib/pgbench extension exposing benchmark
functions in SCHEMA pgbench
Add a new contrib extension `pgbench` that exposes the extracted
distribution, permutation, and hashing functions as SQL-callable
routines in schema `pgbench`:
- pgbench.random(min, max)
- pgbench.random_gaussian(min, max, parameter)
- pgbench.random_exponential(min, max, parameter)
- pgbench.random_zipfian(min, max, parameter)
- pgbench.random_poisson(center)
- pgbench.hash_murmur2(val, seed)
- pgbench.hash_fnv1a(val, seed)
- pgbench.hash(val, seed)
- pgbench.permute(val, size, seed)
- pgbench.setseed(seed)
Includes regression test suite and build definitions for both Make and Meson.
---
contrib/Makefile | 1 +
contrib/meson.build | 1 +
contrib/pgbench/Makefile | 30 ++++
contrib/pgbench/expected/pgbench.out | 153 +++++++++++++++++
contrib/pgbench/meson.build | 36 ++++
contrib/pgbench/pgbench--1.0.sql | 69 ++++++++
contrib/pgbench/pgbench.c | 228 +++++++++++++++++++++++++
contrib/pgbench/pgbench.control | 6 +
contrib/pgbench/sql/pgbench.sql | 52 ++++++
doc/src/sgml/contrib.sgml | 2 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/pgbench-ext.sgml | 240 +++++++++++++++++++++++++++
12 files changed, 819 insertions(+)
create mode 100644 contrib/pgbench/Makefile
create mode 100644 contrib/pgbench/expected/pgbench.out
create mode 100644 contrib/pgbench/meson.build
create mode 100644 contrib/pgbench/pgbench--1.0.sql
create mode 100644 contrib/pgbench/pgbench.c
create mode 100644 contrib/pgbench/pgbench.control
create mode 100644 contrib/pgbench/sql/pgbench.sql
create mode 100644 doc/src/sgml/pgbench-ext.sgml
diff --git a/contrib/Makefile b/contrib/Makefile
index 7d91fe77db3..dd74e9ce996 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -44,6 +44,7 @@ SUBDIRS = \
pgstattuple \
pg_visibility \
pg_walinspect \
+ pgbench \
postgres_fdw \
seg \
spi \
diff --git a/contrib/meson.build b/contrib/meson.build
index ebb7f83d8c5..8fbaa2f12f7 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -58,6 +58,7 @@ subdir('pg_surgery')
subdir('pg_trgm')
subdir('pg_visibility')
subdir('pg_walinspect')
+subdir('pgbench')
subdir('postgres_fdw')
subdir('seg')
subdir('sepgsql')
diff --git a/contrib/pgbench/Makefile b/contrib/pgbench/Makefile
new file mode 100644
index 00000000000..8209d596613
--- /dev/null
+++ b/contrib/pgbench/Makefile
@@ -0,0 +1,30 @@
+# contrib/pgbench/Makefile
+
+MODULE_big = pgbench
+OBJS = \
+ $(WIN32RES) \
+ pgbench.o \
+ pgbench_funcs.o
+
+EXTENSION = pgbench
+DATA = pgbench--1.0.sql
+PGFILEDESC = "pgbench - random distributions and benchmarking utilities"
+
+REGRESS = pgbench
+
+EXTRA_CLEAN = pgbench_funcs.c
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pgbench
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+# pgbench_funcs.c is shared from src/common
+pgbench_funcs.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
diff --git a/contrib/pgbench/expected/pgbench.out b/contrib/pgbench/expected/pgbench.out
new file mode 100644
index 00000000000..ba6be8a79c2
--- /dev/null
+++ b/contrib/pgbench/expected/pgbench.out
@@ -0,0 +1,153 @@
+CREATE EXTENSION pgbench;
+-- Check schema and functions
+\dx+ pgbench
+ Objects in extension "pgbench"
+ Object description
+---------------------------------------------------------------------
+ function pgbench.hash(bigint,bigint)
+ function pgbench.hash_fnv1a(bigint,bigint)
+ function pgbench.hash_murmur2(bigint,bigint)
+ function pgbench.permute(bigint,bigint,bigint)
+ function pgbench.random(bigint,bigint)
+ function pgbench.random_exponential(bigint,bigint,double precision)
+ function pgbench.random_gaussian(bigint,bigint,double precision)
+ function pgbench.random_poisson(double precision)
+ function pgbench.random_zipfian(bigint,bigint,double precision)
+ function pgbench.setseed(bigint)
+ function pgbench.setseed(double precision)
+(11 rows)
+
+-- Test Hashing functions
+SELECT pgbench.hash_murmur2(12345, 0);
+ hash_murmur2
+----------------------
+ -8599128181304237022
+(1 row)
+
+SELECT pgbench.hash_murmur2(12345, 42);
+ hash_murmur2
+---------------------
+ -591810079667318857
+(1 row)
+
+SELECT pgbench.hash_fnv1a(12345, 0);
+ hash_fnv1a
+----------------------
+ -1792800413050876852
+(1 row)
+
+SELECT pgbench.hash_fnv1a(12345, 42);
+ hash_fnv1a
+----------------------
+ -5267424053501251834
+(1 row)
+
+-- pgbench.hash is alias for hash_murmur2
+SELECT pgbench.hash(12345, 0) = pgbench.hash_murmur2(12345, 0);
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT pgbench.hash(12345, 42) = pgbench.hash_murmur2(12345, 42);
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test Permutation function (bijective mapping over 0..9)
+SELECT array_agg(pgbench.permute(i, 10, 42) ORDER BY i) FROM generate_series(0, 9) i;
+ array_agg
+-----------------------
+ {1,7,0,4,3,8,6,5,9,2}
+(1 row)
+
+SELECT count(DISTINCT pgbench.permute(i, 10, 42)) = 10 FROM generate_series(0, 9) i;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT count(DISTINCT pgbench.permute(i, 100, 12345)) = 100 FROM generate_series(0, 99) i;
+ ?column?
+----------
+ t
+(1 row)
+
+-- Test setseed and reproducibility
+SELECT pgbench.setseed(0.5);
+ setseed
+---------
+
+(1 row)
+
+SELECT pgbench.random(1, 100) AS r_unif,
+ pgbench.random_gaussian(1, 100, 2.5) AS r_gauss,
+ pgbench.random_exponential(1, 100, 3.0) AS r_exp,
+ pgbench.random_zipfian(1, 100, 1.5) AS r_zipf,
+ pgbench.random_poisson(50.0) AS r_poiss;
+ r_unif | r_gauss | r_exp | r_zipf | r_poiss
+--------+---------+-------+--------+---------
+ 17 | 60 | 61 | 50 | 36
+(1 row)
+
+-- Reset seed and verify exact same values
+SELECT pgbench.setseed(0.5);
+ setseed
+---------
+
+(1 row)
+
+SELECT pgbench.random(1, 100) AS r_unif,
+ pgbench.random_gaussian(1, 100, 2.5) AS r_gauss,
+ pgbench.random_exponential(1, 100, 3.0) AS r_exp,
+ pgbench.random_zipfian(1, 100, 1.5) AS r_zipf,
+ pgbench.random_poisson(50.0) AS r_poiss;
+ r_unif | r_gauss | r_exp | r_zipf | r_poiss
+--------+---------+-------+--------+---------
+ 17 | 60 | 61 | 50 | 36
+(1 row)
+
+-- Test bigint setseed
+SELECT pgbench.setseed(123456789::bigint);
+ setseed
+---------
+
+(1 row)
+
+SELECT pgbench.random(1, 100);
+ random
+--------
+ 41
+(1 row)
+
+SELECT pgbench.setseed(123456789::bigint);
+ setseed
+---------
+
+(1 row)
+
+SELECT pgbench.random(1, 100);
+ random
+--------
+ 41
+(1 row)
+
+-- Error cases: parameter boundaries
+SELECT pgbench.random(10, 5);
+ERROR: empty range given to random: lower bound 10 is greater than upper bound 5
+SELECT pgbench.random_gaussian(1, 10, 1.5);
+ERROR: gaussian parameter must be at least 2.000000 (not 1.500000)
+SELECT pgbench.random_exponential(1, 10, 0.0);
+ERROR: exponential parameter must be greater than zero (not 0.000000)
+SELECT pgbench.random_zipfian(1, 10, 1.0);
+ERROR: zipfian parameter must be in range [1.001, 1000] (not 1.000000)
+SELECT pgbench.random_zipfian(1, 10, 1001.0);
+ERROR: zipfian parameter must be in range [1.001, 1000] (not 1001.000000)
+SELECT pgbench.random_poisson(0.0);
+ERROR: poisson center parameter must be greater than zero (not 0.000000)
+SELECT pgbench.permute(5, 0, 42);
+ERROR: permute size parameter must be greater than zero
+SELECT pgbench.setseed(1.5);
+ERROR: setseed parameter 1.5 is out of allowed range [-1,1]
+DROP EXTENSION pgbench;
diff --git a/contrib/pgbench/meson.build b/contrib/pgbench/meson.build
new file mode 100644
index 00000000000..256970c9daf
--- /dev/null
+++ b/contrib/pgbench/meson.build
@@ -0,0 +1,36 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+pgbench_ext_sources = files(
+ 'pgbench.c',
+ '../../src/common/pgbench_funcs.c',
+)
+
+if host_system == 'windows'
+ pgbench_ext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'pgbench',
+ '--FILEDESC', 'pgbench - random distributions and benchmarking utilities',])
+endif
+
+pgbench_ext = shared_module('pgbench',
+ pgbench_ext_sources,
+ c_pch: pch_postgres_h,
+ kwargs: contrib_mod_args,
+)
+contrib_targets += pgbench_ext
+
+install_data(
+ 'pgbench.control',
+ 'pgbench--1.0.sql',
+ kwargs: contrib_data_args,
+)
+
+tests += {
+ 'name': 'pgbench_ext',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'pgbench',
+ ],
+ },
+}
diff --git a/contrib/pgbench/pgbench--1.0.sql b/contrib/pgbench/pgbench--1.0.sql
new file mode 100644
index 00000000000..28bdb7cc546
--- /dev/null
+++ b/contrib/pgbench/pgbench--1.0.sql
@@ -0,0 +1,69 @@
+/* contrib/pgbench/pgbench--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pgbench" to load this file. \quit
+
+-- PRNG Seeding
+CREATE FUNCTION setseed(seed double precision)
+RETURNS void
+AS 'MODULE_PATHNAME', 'pgbench_setseed_double'
+LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE;
+
+CREATE FUNCTION setseed(seed bigint)
+RETURNS void
+AS 'MODULE_PATHNAME', 'pgbench_setseed_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE;
+
+-- Uniform Random
+CREATE FUNCTION random(min bigint, max bigint)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_random_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+-- Gaussian (Normal) Random
+CREATE FUNCTION random_gaussian(min bigint, max bigint, parameter double precision)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_random_gaussian_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+-- Exponential Random
+CREATE FUNCTION random_exponential(min bigint, max bigint, parameter double precision)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_random_exponential_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+-- Zipfian Random
+CREATE FUNCTION random_zipfian(min bigint, max bigint, parameter double precision)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_random_zipfian_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+-- Poisson Random
+CREATE FUNCTION random_poisson(center double precision)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_random_poisson_int64'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+-- MurmurHash2 (64-bit)
+CREATE FUNCTION hash_murmur2(val bigint, seed bigint DEFAULT 0)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_hash_murmur2_int64'
+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
+
+-- FNV-1a Hash (64-bit)
+CREATE FUNCTION hash_fnv1a(val bigint, seed bigint DEFAULT 0)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_hash_fnv1a_int64'
+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
+
+-- Alias for hash_murmur2
+CREATE FUNCTION hash(val bigint, seed bigint DEFAULT 0)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_hash_murmur2_int64'
+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
+
+-- Pseudorandom Permutation
+CREATE FUNCTION permute(val bigint, size bigint, seed bigint DEFAULT 0)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pgbench_permute_int64'
+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
new file mode 100644
index 00000000000..f0cb3cb5679
--- /dev/null
+++ b/contrib/pgbench/pgbench.c
@@ -0,0 +1,228 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgbench.c
+ * Server-side extension functions exposing pgbench random distributions,
+ * permutation, and hashing functions.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgbench/pgbench.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "common/int.h"
+#include "common/pgbench_funcs.h"
+#include "common/pg_prng.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+/* Shared PRNG state used by pgbench extension random functions */
+static pg_prng_state pgbench_prng_state;
+static bool pgbench_prng_seed_set = false;
+
+/*
+ * Initialize (seed) the PRNG, if not done yet in this backend process.
+ */
+static void
+initialize_prng(void)
+{
+ if (unlikely(!pgbench_prng_seed_set))
+ {
+ if (unlikely(!pg_prng_strong_seed(&pgbench_prng_state)))
+ {
+ TimestampTz now = GetCurrentTimestamp();
+ uint64 iseed;
+
+ /* Mix the PID with the most predictable bits of the timestamp */
+ iseed = (uint64) now ^ ((uint64) MyProcPid << 32);
+ pg_prng_seed(&pgbench_prng_state, iseed);
+ }
+ pgbench_prng_seed_set = true;
+ }
+}
+
+static inline void
+check_random_range(int64 min, int64 max)
+{
+ int64 delta;
+
+ if (unlikely(min > max))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("empty range given to random: lower bound " INT64_FORMAT " is greater than upper bound " INT64_FORMAT,
+ min, max)));
+
+ if (unlikely(pg_sub_s64_overflow(max, min, &delta) ||
+ pg_add_s64_overflow(delta, 1, &delta)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("random range is too large")));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_setseed_double);
+Datum
+pgbench_setseed_double(PG_FUNCTION_ARGS)
+{
+ float8 seed = PG_GETARG_FLOAT8(0);
+
+ if (seed < -1.0 || seed > 1.0 || isnan(seed))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("setseed parameter %g is out of allowed range [-1,1]",
+ seed)));
+
+ pg_prng_fseed(&pgbench_prng_state, seed);
+ pgbench_prng_seed_set = true;
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(pgbench_setseed_int64);
+Datum
+pgbench_setseed_int64(PG_FUNCTION_ARGS)
+{
+ int64 seed = PG_GETARG_INT64(0);
+
+ pg_prng_seed(&pgbench_prng_state, (uint64) seed);
+ pgbench_prng_seed_set = true;
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(pgbench_random_int64);
+Datum
+pgbench_random_int64(PG_FUNCTION_ARGS)
+{
+ int64 min = PG_GETARG_INT64(0);
+ int64 max = PG_GETARG_INT64(1);
+
+ check_random_range(min, max);
+ initialize_prng();
+
+ PG_RETURN_INT64(pgbench_random(&pgbench_prng_state, min, max));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_random_gaussian_int64);
+Datum
+pgbench_random_gaussian_int64(PG_FUNCTION_ARGS)
+{
+ int64 min = PG_GETARG_INT64(0);
+ int64 max = PG_GETARG_INT64(1);
+ float8 param = PG_GETARG_FLOAT8(2);
+
+ check_random_range(min, max);
+
+ if (isnan(param) || param < PGBENCH_MIN_GAUSSIAN_PARAM)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gaussian parameter must be at least %f (not %f)",
+ PGBENCH_MIN_GAUSSIAN_PARAM, param)));
+
+ initialize_prng();
+
+ PG_RETURN_INT64(pgbench_random_gaussian(&pgbench_prng_state, min, max, param));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_random_exponential_int64);
+Datum
+pgbench_random_exponential_int64(PG_FUNCTION_ARGS)
+{
+ int64 min = PG_GETARG_INT64(0);
+ int64 max = PG_GETARG_INT64(1);
+ float8 param = PG_GETARG_FLOAT8(2);
+
+ check_random_range(min, max);
+
+ if (isnan(param) || param <= 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("exponential parameter must be greater than zero (not %f)",
+ param)));
+
+ initialize_prng();
+
+ PG_RETURN_INT64(pgbench_random_exponential(&pgbench_prng_state, min, max, param));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_random_zipfian_int64);
+Datum
+pgbench_random_zipfian_int64(PG_FUNCTION_ARGS)
+{
+ int64 min = PG_GETARG_INT64(0);
+ int64 max = PG_GETARG_INT64(1);
+ float8 param = PG_GETARG_FLOAT8(2);
+
+ check_random_range(min, max);
+
+ if (isnan(param) || param < PGBENCH_MIN_ZIPFIAN_PARAM || param > PGBENCH_MAX_ZIPFIAN_PARAM)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("zipfian parameter must be in range [%.3f, %.0f] (not %f)",
+ PGBENCH_MIN_ZIPFIAN_PARAM, PGBENCH_MAX_ZIPFIAN_PARAM, param)));
+
+ initialize_prng();
+
+ PG_RETURN_INT64(pgbench_random_zipfian(&pgbench_prng_state, min, max, param));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_random_poisson_int64);
+Datum
+pgbench_random_poisson_int64(PG_FUNCTION_ARGS)
+{
+ float8 center = PG_GETARG_FLOAT8(0);
+
+ if (isnan(center) || center <= 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("poisson center parameter must be greater than zero (not %f)",
+ center)));
+
+ initialize_prng();
+
+ PG_RETURN_INT64(pgbench_random_poisson(&pgbench_prng_state, center));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_hash_murmur2_int64);
+Datum
+pgbench_hash_murmur2_int64(PG_FUNCTION_ARGS)
+{
+ int64 val = PG_GETARG_INT64(0);
+ int64 seed = PG_GETARG_INT64(1);
+
+ PG_RETURN_INT64(pgbench_hash_murmur2(val, (uint64) seed));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_hash_fnv1a_int64);
+Datum
+pgbench_hash_fnv1a_int64(PG_FUNCTION_ARGS)
+{
+ int64 val = PG_GETARG_INT64(0);
+ int64 seed = PG_GETARG_INT64(1);
+
+ PG_RETURN_INT64(pgbench_hash_fnv1a(val, (uint64) seed));
+}
+
+PG_FUNCTION_INFO_V1(pgbench_permute_int64);
+Datum
+pgbench_permute_int64(PG_FUNCTION_ARGS)
+{
+ int64 val = PG_GETARG_INT64(0);
+ int64 size = PG_GETARG_INT64(1);
+ int64 seed = PG_GETARG_INT64(2);
+
+ if (size <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("permute size parameter must be greater than zero")));
+
+ PG_RETURN_INT64(pgbench_permute(val, size, seed));
+}
diff --git a/contrib/pgbench/pgbench.control b/contrib/pgbench/pgbench.control
new file mode 100644
index 00000000000..0c38189cb99
--- /dev/null
+++ b/contrib/pgbench/pgbench.control
@@ -0,0 +1,6 @@
+# pgbench extension
+comment = 'pgbench distribution, permutation, and hashing functions'
+default_version = '1.0'
+module_pathname = '$libdir/pgbench'
+relocatable = false
+schema = 'pgbench'
diff --git a/contrib/pgbench/sql/pgbench.sql b/contrib/pgbench/sql/pgbench.sql
new file mode 100644
index 00000000000..a966f56f433
--- /dev/null
+++ b/contrib/pgbench/sql/pgbench.sql
@@ -0,0 +1,52 @@
+CREATE EXTENSION pgbench;
+
+-- Check schema and functions
+\dx+ pgbench
+
+-- Test Hashing functions
+SELECT pgbench.hash_murmur2(12345, 0);
+SELECT pgbench.hash_murmur2(12345, 42);
+SELECT pgbench.hash_fnv1a(12345, 0);
+SELECT pgbench.hash_fnv1a(12345, 42);
+-- pgbench.hash is alias for hash_murmur2
+SELECT pgbench.hash(12345, 0) = pgbench.hash_murmur2(12345, 0);
+SELECT pgbench.hash(12345, 42) = pgbench.hash_murmur2(12345, 42);
+
+-- Test Permutation function (bijective mapping over 0..9)
+SELECT array_agg(pgbench.permute(i, 10, 42) ORDER BY i) FROM generate_series(0, 9) i;
+SELECT count(DISTINCT pgbench.permute(i, 10, 42)) = 10 FROM generate_series(0, 9) i;
+SELECT count(DISTINCT pgbench.permute(i, 100, 12345)) = 100 FROM generate_series(0, 99) i;
+
+-- Test setseed and reproducibility
+SELECT pgbench.setseed(0.5);
+SELECT pgbench.random(1, 100) AS r_unif,
+ pgbench.random_gaussian(1, 100, 2.5) AS r_gauss,
+ pgbench.random_exponential(1, 100, 3.0) AS r_exp,
+ pgbench.random_zipfian(1, 100, 1.5) AS r_zipf,
+ pgbench.random_poisson(50.0) AS r_poiss;
+
+-- Reset seed and verify exact same values
+SELECT pgbench.setseed(0.5);
+SELECT pgbench.random(1, 100) AS r_unif,
+ pgbench.random_gaussian(1, 100, 2.5) AS r_gauss,
+ pgbench.random_exponential(1, 100, 3.0) AS r_exp,
+ pgbench.random_zipfian(1, 100, 1.5) AS r_zipf,
+ pgbench.random_poisson(50.0) AS r_poiss;
+
+-- Test bigint setseed
+SELECT pgbench.setseed(123456789::bigint);
+SELECT pgbench.random(1, 100);
+SELECT pgbench.setseed(123456789::bigint);
+SELECT pgbench.random(1, 100);
+
+-- Error cases: parameter boundaries
+SELECT pgbench.random(10, 5);
+SELECT pgbench.random_gaussian(1, 10, 1.5);
+SELECT pgbench.random_exponential(1, 10, 0.0);
+SELECT pgbench.random_zipfian(1, 10, 1.0);
+SELECT pgbench.random_zipfian(1, 10, 1001.0);
+SELECT pgbench.random_poisson(0.0);
+SELECT pgbench.permute(5, 0, 42);
+SELECT pgbench.setseed(1.5);
+
+DROP EXTENSION pgbench;
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index b9b03654aad..0edd60b4a66 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -100,6 +100,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>;
<member><xref linkend="lo"/></member>
<member><xref linkend="ltree"/></member>
<member><xref linkend="pgcrypto"/></member>
+ <member><xref linkend="pgbench-ext"/></member>
<member><xref linkend="pgtrgm"/></member>
<member><xref linkend="seg"/></member>
<member><xref linkend="tablefunc"/></member>
@@ -166,6 +167,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>;
&pgtrgm;
&pgvisibility;
&pgwalinspect;
+ &pgbench-ext;
&postgres-fdw;
&seg;
&sepgsql;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 66ea8b988a1..0ae47865d12 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -159,6 +159,7 @@
<!ENTITY pgtrgm SYSTEM "pgtrgm.sgml">
<!ENTITY pgvisibility SYSTEM "pgvisibility.sgml">
<!ENTITY pgwalinspect SYSTEM "pgwalinspect.sgml">
+<!ENTITY pgbench-ext SYSTEM "pgbench-ext.sgml">
<!ENTITY postgres-fdw SYSTEM "postgres-fdw.sgml">
<!ENTITY seg SYSTEM "seg.sgml">
<!ENTITY contrib-spi SYSTEM "contrib-spi.sgml">
diff --git a/doc/src/sgml/pgbench-ext.sgml b/doc/src/sgml/pgbench-ext.sgml
new file mode 100644
index 00000000000..bbd94388457
--- /dev/null
+++ b/doc/src/sgml/pgbench-ext.sgml
@@ -0,0 +1,240 @@
+<!-- doc/src/sgml/pgbench-ext.sgml -->
+
+<sect1 id="pgbench-ext" xreflabel="pgbench (extension)">
+ <title>pgbench — benchmark random distributions, hashing, and permutation functions</title>
+
+ <indexterm zone="pgbench-ext">
+ <primary>pgbench</primary>
+ <secondary>extension</secondary>
+ </indexterm>
+
+ <para>
+ The <filename>pgbench</filename> module provides SQL-callable functions
+ implementing the statistical random distribution generators, domain
+ permutations, and 64-bit hashing routines from the
+ <xref linkend="pgbench"/> benchmarking tool.
+ </para>
+
+ <para>
+ These functions are installed into the <literal>pgbench</literal> schema and
+ enable reproducible server-side synthetic data generation, procedural
+ workload simulation, and client/server algorithmic parity in custom test
+ harnesses.
+ </para>
+
+ <para>
+ This module is considered <quote>trusted</quote>, that is, it can be
+ installed by non-superusers who have <literal>CREATE</literal> privilege
+ on the current database.
+ </para>
+
+ <sect2 id="pgbench-ext-funcs">
+ <title>Functions</title>
+
+ <para>
+ All functions provided by this extension are installed in the
+ <literal>pgbench</literal> schema.
+ </para>
+
+ <variablelist>
+ <varlistentry id="pgbench-ext-funcs-setseed">
+ <term>
+ <function>pgbench.setseed(seed double precision) returns void</function>
+ <indexterm>
+ <primary>setseed</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <term>
+ <function>pgbench.setseed(seed bigint) returns void</function>
+ </term>
+ <listitem>
+ <para>
+ Sets the seed for the backend's internal pseudorandom number generator used
+ by the <literal>pgbench</literal> random functions. When passed a
+ <type>double precision</type> value, it must be in the range
+ <literal>[-1.0, 1.0]</literal>. When passed a <type>bigint</type> value,
+ it initializes the full 64-bit state directly.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-random">
+ <term>
+ <function>pgbench.random(min bigint, max bigint) returns bigint</function>
+ <indexterm>
+ <primary>random</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Generates a uniformly-distributed random integer between
+ <parameter>min</parameter> and <parameter>max</parameter>, inclusive.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-random-gaussian">
+ <term>
+ <function>pgbench.random_gaussian(min bigint, max bigint, parameter double precision) returns bigint</function>
+ <indexterm>
+ <primary>random_gaussian</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Generates a Gaussian-distributed random integer between
+ <parameter>min</parameter> and <parameter>max</parameter>, inclusive.
+ The <parameter>parameter</parameter> defines how concentrated the values
+ are toward the center of the range and must be at least <literal>2.0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-random-exponential">
+ <term>
+ <function>pgbench.random_exponential(min bigint, max bigint, parameter double precision) returns bigint</function>
+ <indexterm>
+ <primary>random_exponential</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Generates an exponentially-distributed random integer between
+ <parameter>min</parameter> and <parameter>max</parameter>, inclusive.
+ The <parameter>parameter</parameter> controls the distribution density and
+ must be greater than <literal>0.0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-random-zipfian">
+ <term>
+ <function>pgbench.random_zipfian(min bigint, max bigint, parameter double precision) returns bigint</function>
+ <indexterm>
+ <primary>random_zipfian</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Generates a Zipfian-distributed random integer between
+ <parameter>min</parameter> and <parameter>max</parameter>, inclusive.
+ The <parameter>parameter</parameter> (Zipfian skew factor <literal>s</literal>)
+ must be in the range <literal>[1.001, 1000.0]</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-random-poisson">
+ <term>
+ <function>pgbench.random_poisson(center double precision) returns bigint</function>
+ <indexterm>
+ <primary>random_poisson</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Generates a Poisson-distributed random integer centered on
+ <parameter>center</parameter>, which must be greater than <literal>0.0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-hash-murmur2">
+ <term>
+ <function>pgbench.hash_murmur2(val bigint [, seed bigint ]) returns bigint</function>
+ <indexterm>
+ <primary>hash_murmur2</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Computes the 64-bit Austin Appleby MurmurHash2 of <parameter>val</parameter>
+ using optional <parameter>seed</parameter> (default is <literal>0</literal>).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-hash-fnv1a">
+ <term>
+ <function>pgbench.hash_fnv1a(val bigint [, seed bigint ]) returns bigint</function>
+ <indexterm>
+ <primary>hash_fnv1a</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Computes the 64-bit Fowler–Noll–Vo 1a hash of
+ <parameter>val</parameter> using optional <parameter>seed</parameter>
+ (default is <literal>0</literal>).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-hash">
+ <term>
+ <function>pgbench.hash(val bigint [, seed bigint ]) returns bigint</function>
+ <indexterm>
+ <primary>hash</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ An alias for <function>pgbench.hash_murmur2</function>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="pgbench-ext-funcs-permute">
+ <term>
+ <function>pgbench.permute(val bigint, size bigint [, seed bigint ]) returns bigint</function>
+ <indexterm>
+ <primary>permute</primary>
+ <secondary>in pgbench</secondary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Permutes <parameter>val</parameter> into the range
+ <literal>[0, size)</literal> using a multi-round Feistel-style bijective
+ permutation seeded with <parameter>seed</parameter> (default is
+ <literal>0</literal>). <parameter>size</parameter> must be greater than
+ <literal>0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2 id="pgbench-ext-examples">
+ <title>Examples</title>
+
+ <para>
+ Generate a reproducible synthetic customer workload directly within SQL:
+ </para>
+
+<programlisting>
+CREATE EXTENSION pgbench;
+
+-- Seed PRNG for reproducible test run
+SELECT pgbench.setseed(42::bigint);
+
+-- Generate non-uniform account IDs following a Zipfian distribution
+SELECT pgbench.random_zipfian(1, 100000, 1.2) AS aid
+FROM generate_series(1, 10);
+
+-- Generate a unique permutation of customer IDs from 0 to 999
+SELECT pgbench.permute(i, 1000, 12345) AS permuted_id
+FROM generate_series(0, 999) i;
+</programlisting>
+ </sect2>
+
+</sect1>
--
2.55.0.897.gb25b4bd76c-goog
From 31e8e96c92ea89655137083e71176c01ae8dc445 Mon Sep 17 00:00:00 2001
From: Hannu Krosing <[email protected]>
Date: Tue, 25 Aug 2026 22:07:35 +0000
Subject: [PATCH v2 1/2] Extract pgbench low-level functions into src/common
Move uniform, Gaussian, Exponential, Zipfian, and Poisson random
distributions, 64-bit MurmurHash2 and FNV-1a hash functions, and
domain permutation from src/bin/pgbench/pgbench.c into a shared
library module in src/common/pgbench_funcs.c and
src/include/common/pgbench_funcs.h.
Update pgbench to include common/pgbench_funcs.h and use the shared
implementations, removing duplicate code.
---
src/bin/pgbench/pgbench.c | 342 ++---------------------------
src/common/Makefile | 1 +
src/common/meson.build | 1 +
src/common/pgbench_funcs.c | 320 +++++++++++++++++++++++++++
src/include/common/pgbench_funcs.h | 48 ++++
5 files changed, 386 insertions(+), 326 deletions(-)
create mode 100644 src/common/pgbench_funcs.c
create mode 100644 src/include/common/pgbench_funcs.h
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 5862758427f..f38575bdccb 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -57,6 +57,7 @@
#include "common/int.h"
#include "common/logging.h"
#include "common/pg_prng.h"
+#include "common/pgbench_funcs.h"
#include "common/string.h"
#include "common/username.h"
#include "fe_utils/cancel.h"
@@ -78,15 +79,6 @@
#define ERRCODE_T_R_DEADLOCK_DETECTED "40P01"
#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 socket set implementations
*/
@@ -166,11 +158,6 @@ typedef struct socket_set
#define LOG_STEP_SECONDS 5 /* seconds between log messages */
#define DEFAULT_NXACTS 10 /* default nxacts */
-#define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */
-
-#define MIN_ZIPFIAN_PARAM 1.001 /* minimum parameter for zipfian */
-#define MAX_ZIPFIAN_PARAM 1000.0 /* maximum parameter for zipfian */
-
static int nxacts = 0; /* number of transactions per client */
static int duration = 0; /* duration in seconds */
static int64 end_time = 0; /* when to stop in micro seconds, under -T */
@@ -1081,303 +1068,6 @@ initRandomState(pg_prng_state *state)
pg_prng_seed(state, pg_prng_uint64(&base_random_sequence));
}
-
-/*
- * random number generator: uniform distribution from min to max inclusive.
- *
- * Although the limits are expressed as int64, you can't generate the full
- * int64 range in one call, because the difference of the limits mustn't
- * overflow int64. This is not checked.
- */
-static int64
-getrand(pg_prng_state *state, int64 min, int64 max)
-{
- return min + (int64) pg_prng_uint64_range(state, 0, max - min);
-}
-
-/*
- * 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(pg_prng_state *state, 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);
- /* pg_prng_double value in [0, 1), uniform in (0, 1] */
- uniform = 1.0 - pg_prng_double(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(pg_prng_state *state, 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 normally-distributed random number in the range -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
- {
- stdev = pg_prng_double_normal(state);
- }
- 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.
- *
- * Individual results are rounded to integers, though the center value need
- * not be one.
- */
-static int64
-getPoissonRand(pg_prng_state *state, double center)
-{
- /*
- * Use inverse transform sampling to generate a value > 0, such that the
- * expected (i.e. average) value is the given argument.
- */
- double uniform;
-
- /* pg_prng_double value in [0, 1), uniform in (0, 1] */
- uniform = 1.0 - pg_prng_double(state);
-
- return (int64) (-log(uniform) * center + 0.5);
-}
-
-/*
- * Computing zipfian using rejection method, based on
- * "Non-Uniform Random Variate Generation",
- * Luc Devroye, p. 550-551, Springer 1986.
- *
- * This works for s > 1.0, but may perform badly for s very close to 1.0.
- */
-static int64
-computeIterativeZipfian(pg_prng_state *state, int64 n, double s)
-{
- double b = pow(2.0, s - 1.0);
- double x,
- t,
- u,
- v;
-
- /* Ensure n is sane */
- if (n <= 1)
- return 1;
-
- while (true)
- {
- /* random variates */
- u = pg_prng_double(state);
- v = pg_prng_double(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;
-}
-
-/* random number generator: zipfian distribution from min to max inclusive */
-static int64
-getZipfianRand(pg_prng_state *state, int64 min, int64 max, double s)
-{
- int64 n = max - min + 1;
-
- /* abort if parameter is invalid */
- Assert(MIN_ZIPFIAN_PARAM <= s && s <= MAX_ZIPFIAN_PARAM);
-
- return min - 1 + computeIterativeZipfian(state, 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;
-}
-
-/*
- * Pseudorandom permutation function
- *
- * For small sizes, this generates each of the (size!) possible permutations
- * of integers in the range [0, size) with roughly equal probability. Once
- * the size is larger than 20, the number of possible permutations exceeds the
- * number of distinct states of the internal pseudorandom number generator,
- * and so not all possible permutations can be generated, but the permutations
- * chosen should continue to give the appearance of being random.
- *
- * THIS FUNCTION IS NOT CRYPTOGRAPHICALLY SECURE.
- * DO NOT USE FOR SUCH PURPOSE.
- */
-static int64
-permute(const int64 val, const int64 isize, const int64 seed)
-{
- /* using a high-end PRNG is probably overkill */
- pg_prng_state state;
- uint64 size;
- uint64 v;
- int masklen;
- uint64 mask;
- int i;
-
- if (isize < 2)
- return 0; /* nothing to permute */
-
- /* Initialize prng state using the seed */
- pg_prng_seed(&state, (uint64) seed);
-
- /* Computations are performed on unsigned values */
- size = (uint64) isize;
- v = (uint64) val % size;
-
- /* Mask to work modulo largest power of 2 less than or equal to size */
- masklen = pg_leftmost_one_pos64(size);
- mask = (((uint64) 1) << masklen) - 1;
-
- /*
- * Permute the input value by applying several rounds of pseudorandom
- * bijective transformations. The intention here is to distribute each
- * input uniformly randomly across the range, and separate adjacent inputs
- * approximately uniformly randomly from each other, leading to a fairly
- * random overall choice of permutation.
- *
- * To separate adjacent inputs, we multiply by a random number modulo
- * (mask + 1), which is a power of 2. For this to be a bijection, the
- * multiplier must be odd. Since this is known to lead to less randomness
- * in the lower bits, we also apply a rotation that shifts the topmost bit
- * into the least significant bit. In the special cases where size <= 3,
- * mask = 1 and each of these operations is actually a no-op, so we also
- * XOR the value with a different random number to inject additional
- * randomness. Since the size is generally not a power of 2, we apply
- * this bijection on overlapping upper and lower halves of the input.
- *
- * To distribute the inputs uniformly across the range, we then also apply
- * a random offset modulo the full range.
- *
- * Taken together, these operations resemble a modified linear
- * congruential generator, as is commonly used in pseudorandom number
- * generators. The number of rounds is fairly arbitrary, but six has been
- * found empirically to give a fairly good tradeoff between performance
- * and uniform randomness. For small sizes it selects each of the (size!)
- * possible permutations with roughly equal probability. For larger
- * sizes, not all permutations can be generated, but the intended random
- * spread is still produced.
- */
- for (i = 0; i < 6; i++)
- {
- uint64 m,
- r,
- t;
-
- /* Random multiply (by an odd number), XOR and rotate of lower half */
- m = (pg_prng_uint64(&state) & mask) | 1;
- r = pg_prng_uint64(&state) & mask;
- if (v <= mask)
- {
- v = ((v * m) ^ r) & mask;
- v = ((v << 1) & mask) | (v >> (masklen - 1));
- }
-
- /* Random multiply (by an odd number), XOR and rotate of upper half */
- m = (pg_prng_uint64(&state) & mask) | 1;
- r = pg_prng_uint64(&state) & mask;
- t = size - 1 - v;
- if (t <= mask)
- {
- t = ((t * m) ^ r) & mask;
- t = ((t << 1) & mask) | (t >> (masklen - 1));
- v = size - 1 - t;
- }
-
- /* Random offset */
- r = pg_prng_uint64_range(&state, 0, size - 1);
- v = (v + r) % size;
- }
-
- return (int64) v;
-}
-
/*
* Initialize the given SimpleStats struct to all zeroes
*/
@@ -2665,7 +2355,7 @@ evalStandardFunc(CState *st,
if (func == PGBENCH_RANDOM)
{
Assert(nargs == 2);
- setIntValue(retval, getrand(&st->cs_func_rs, imin, imax));
+ setIntValue(retval, pgbench_random(&st->cs_func_rs, imin, imax));
}
else /* gaussian & exponential */
{
@@ -2678,28 +2368,28 @@ evalStandardFunc(CState *st,
if (func == PGBENCH_RANDOM_GAUSSIAN)
{
- if (param < MIN_GAUSSIAN_PARAM)
+ if (param < PGBENCH_MIN_GAUSSIAN_PARAM)
{
pg_log_error("gaussian parameter must be at least %f (not %f)",
- MIN_GAUSSIAN_PARAM, param);
+ PGBENCH_MIN_GAUSSIAN_PARAM, param);
return false;
}
setIntValue(retval,
- getGaussianRand(&st->cs_func_rs,
- imin, imax, param));
+ pgbench_random_gaussian(&st->cs_func_rs,
+ imin, imax, param));
}
else if (func == PGBENCH_RANDOM_ZIPFIAN)
{
- if (param < MIN_ZIPFIAN_PARAM || param > MAX_ZIPFIAN_PARAM)
+ if (param < PGBENCH_MIN_ZIPFIAN_PARAM || param > PGBENCH_MAX_ZIPFIAN_PARAM)
{
pg_log_error("zipfian parameter must be in range [%.3f, %.0f] (not %f)",
- MIN_ZIPFIAN_PARAM, MAX_ZIPFIAN_PARAM, param);
+ PGBENCH_MIN_ZIPFIAN_PARAM, PGBENCH_MAX_ZIPFIAN_PARAM, param);
return false;
}
setIntValue(retval,
- getZipfianRand(&st->cs_func_rs, imin, imax, param));
+ pgbench_random_zipfian(&st->cs_func_rs, imin, imax, param));
}
else /* exponential */
{
@@ -2711,8 +2401,8 @@ evalStandardFunc(CState *st,
}
setIntValue(retval,
- getExponentialRand(&st->cs_func_rs,
- imin, imax, param));
+ pgbench_random_exponential(&st->cs_func_rs,
+ imin, imax, param));
}
}
@@ -2765,9 +2455,9 @@ evalStandardFunc(CState *st,
return false;
if (func == PGBENCH_HASH_MURMUR2)
- setIntValue(retval, getHashMurmur2(val, seed));
+ setIntValue(retval, pgbench_hash_murmur2(val, seed));
else if (func == PGBENCH_HASH_FNV1A)
- setIntValue(retval, getHashFnv1a(val, seed));
+ setIntValue(retval, pgbench_hash_fnv1a(val, seed));
else
/* cannot get here */
Assert(0);
@@ -2794,7 +2484,7 @@ evalStandardFunc(CState *st,
return false;
}
- setIntValue(retval, permute(val, size, seed));
+ setIntValue(retval, pgbench_permute(val, size, seed));
return true;
}
@@ -3049,7 +2739,7 @@ chooseScript(TState *thread)
if (num_scripts == 1)
return 0;
- w = getrand(&thread->ts_choose_rs, 0, total_weight - 1);
+ w = pgbench_random(&thread->ts_choose_rs, 0, total_weight - 1);
do
{
w -= sql_script[i++].weight;
@@ -3782,7 +3472,7 @@ advanceConnectionState(TState *thread, CState *st, StatsData *agg)
Assert(throttle_delay > 0);
thread->throttle_trigger +=
- getPoissonRand(&thread->ts_throttle_rs, throttle_delay);
+ pgbench_random_poisson(&thread->ts_throttle_rs, throttle_delay);
st->txn_scheduled = thread->throttle_trigger;
/*
diff --git a/src/common/Makefile b/src/common/Makefile
index 1a2fbbe887f..3a2f90a3861 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -71,6 +71,7 @@ OBJS_COMMON = \
pg_get_line.o \
pg_lzcompress.o \
pg_prng.o \
+ pgbench_funcs.o \
pgfnames.o \
psprintf.o \
relpath.o \
diff --git a/src/common/meson.build b/src/common/meson.build
index 9bd55cda95b..72e8fa57ea2 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -25,6 +25,7 @@ common_sources = files(
'pg_get_line.c',
'pg_lzcompress.c',
'pg_prng.c',
+ 'pgbench_funcs.c',
'pgfnames.c',
'psprintf.c',
'relpath.c',
diff --git a/src/common/pgbench_funcs.c b/src/common/pgbench_funcs.c
new file mode 100644
index 00000000000..4076262dc28
--- /dev/null
+++ b/src/common/pgbench_funcs.c
@@ -0,0 +1,320 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgbench_funcs.c
+ * Shared random distribution, permutation, and hashing functions.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/pgbench_funcs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <math.h>
+
+#include "common/pgbench_funcs.h"
+#include "port/pg_bitutils.h"
+
+/*
+ * random number generator: uniform distribution from min to max inclusive.
+ *
+ * Although the limits are expressed as int64, you can't generate the full
+ * int64 range in one call, because the difference of the limits mustn't
+ * overflow int64. This is not checked here; callers should check.
+ */
+int64
+pgbench_random(pg_prng_state *state, int64 min, int64 max)
+{
+ return min + (int64) pg_prng_uint64_range(state, 0, max - min);
+}
+
+/*
+ * 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).
+ */
+int64
+pgbench_random_exponential(pg_prng_state *state, 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);
+ /* pg_prng_double value in [0, 1), uniform in (0, 1] */
+ uniform = 1.0 - pg_prng_double(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 */
+int64
+pgbench_random_gaussian(pg_prng_state *state, int64 min, int64 max,
+ double parameter)
+{
+ double stdev;
+ double rand;
+
+ /* abort if parameter is too low, but must really be checked beforehand */
+ Assert(parameter >= PGBENCH_MIN_GAUSSIAN_PARAM);
+
+ /*
+ * Get normally-distributed random number in the range -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
+ {
+ stdev = pg_prng_double_normal(state);
+ }
+ 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.
+ *
+ * Individual results are rounded to integers, though the center value need
+ * not be one.
+ */
+int64
+pgbench_random_poisson(pg_prng_state *state, double center)
+{
+ /*
+ * Use inverse transform sampling to generate a value > 0, such that the
+ * expected (i.e. average) value is the given argument.
+ */
+ double uniform;
+
+ /* pg_prng_double value in [0, 1), uniform in (0, 1] */
+ uniform = 1.0 - pg_prng_double(state);
+
+ return (int64) (-log(uniform) * center + 0.5);
+}
+
+/*
+ * Computing zipfian using rejection method, based on
+ * "Non-Uniform Random Variate Generation",
+ * Luc Devroye, p. 550-551, Springer 1986.
+ *
+ * This works for s > 1.0, but may perform badly for s very close to 1.0.
+ */
+static int64
+computeIterativeZipfian(pg_prng_state *state, int64 n, double s)
+{
+ double b = pow(2.0, s - 1.0);
+ double x,
+ t,
+ u,
+ v;
+
+ /* Ensure n is sane */
+ if (n <= 1)
+ return 1;
+
+ while (true)
+ {
+ /* random variates */
+ u = pg_prng_double(state);
+ v = pg_prng_double(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;
+}
+
+/* random number generator: zipfian distribution from min to max inclusive */
+int64
+pgbench_random_zipfian(pg_prng_state *state, int64 min, int64 max, double s)
+{
+ int64 n = max - min + 1;
+
+ /* abort if parameter is invalid */
+ Assert(PGBENCH_MIN_ZIPFIAN_PARAM <= s && s <= PGBENCH_MAX_ZIPFIAN_PARAM);
+
+ return min - 1 + computeIterativeZipfian(state, n, s);
+}
+
+/*
+ * FNV-1a hash function
+ */
+int64
+pgbench_hash_fnv1a(int64 val, uint64 seed)
+{
+ int64 result;
+ int i;
+
+ result = PGBENCH_FNV_OFFSET_BASIS ^ seed;
+ for (i = 0; i < 8; ++i)
+ {
+ int32 octet = val & 0xff;
+
+ val = val >> 8;
+ result = result ^ octet;
+ result = result * PGBENCH_FNV_PRIME;
+ }
+
+ return result;
+}
+
+/*
+ * Murmur2 hash function
+ *
+ * Based on original work of Austin Appleby
+ * https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp
+ */
+int64
+pgbench_hash_murmur2(int64 val, uint64 seed)
+{
+ uint64 result = seed ^ PGBENCH_MM2_MUL_TIMES_8; /* sizeof(int64) */
+ uint64 k = (uint64) val;
+
+ k *= PGBENCH_MM2_MUL;
+ k ^= k >> PGBENCH_MM2_ROT;
+ k *= PGBENCH_MM2_MUL;
+
+ result ^= k;
+ result *= PGBENCH_MM2_MUL;
+
+ result ^= result >> PGBENCH_MM2_ROT;
+ result *= PGBENCH_MM2_MUL;
+ result ^= result >> PGBENCH_MM2_ROT;
+
+ return (int64) result;
+}
+
+/*
+ * Pseudorandom permutation function
+ *
+ * For small sizes, this generates each of the (size!) possible permutations
+ * of integers in the range [0, size) with roughly equal probability. Once
+ * the size is larger than 20, the number of possible permutations exceeds the
+ * number of distinct states of the internal pseudorandom number generator,
+ * and so not all possible permutations can be generated, but the permutations
+ * chosen should continue to give the appearance of being random.
+ *
+ * THIS FUNCTION IS NOT CRYPTOGRAPHICALLY SECURE.
+ * DO NOT USE FOR SUCH PURPOSE.
+ */
+int64
+pgbench_permute(const int64 val, const int64 isize, const int64 seed)
+{
+ /* using a high-end PRNG is probably overkill */
+ pg_prng_state state;
+ uint64 size;
+ uint64 v;
+ int masklen;
+ uint64 mask;
+ int i;
+
+ if (isize < 2)
+ return 0; /* nothing to permute */
+
+ /* Initialize prng state using the seed */
+ pg_prng_seed(&state, (uint64) seed);
+
+ /* Computations are performed on unsigned values */
+ size = (uint64) isize;
+ v = (uint64) val % size;
+
+ /* Mask to work modulo largest power of 2 less than or equal to size */
+ masklen = pg_leftmost_one_pos64(size);
+ mask = (((uint64) 1) << masklen) - 1;
+
+ /*
+ * Permute the input value by applying several rounds of pseudorandom
+ * bijective transformations. The intention here is to distribute each
+ * input uniformly randomly across the range, and separate adjacent inputs
+ * approximately uniformly randomly from each other, leading to a fairly
+ * random overall choice of permutation.
+ *
+ * To separate adjacent inputs, we multiply by a random number modulo
+ * (mask + 1), which is a power of 2. For this to be a bijection, the
+ * multiplier must be odd. Since this is known to lead to less randomness
+ * in the lower bits, we also apply a rotation that shifts the topmost bit
+ * into the least significant bit. In the special cases where size <= 3,
+ * mask = 1 and each of these operations is actually a no-op, so we also
+ * XOR the value with a different random number to inject additional
+ * randomness. Since the size is generally not a power of 2, we apply
+ * this bijection on overlapping upper and lower halves of the input.
+ *
+ * To distribute the inputs uniformly across the range, we then also apply
+ * a random offset modulo the full range.
+ *
+ * Taken together, these operations resemble a modified linear
+ * congruential generator, as is commonly used in pseudorandom number
+ * generators. The number of rounds is fairly arbitrary, but six has been
+ * found empirically to give a fairly good tradeoff between performance
+ * and uniform randomness. For small sizes it selects each of the (size!)
+ * possible permutations with roughly equal probability. For larger
+ * sizes, not all permutations can be generated, but the intended random
+ * spread is still produced.
+ */
+ for (i = 0; i < 6; i++)
+ {
+ uint64 m,
+ r,
+ t;
+
+ /* Random multiply (by an odd number), XOR and rotate of lower half */
+ m = (pg_prng_uint64(&state) & mask) | 1;
+ r = pg_prng_uint64(&state) & mask;
+ if (v <= mask)
+ {
+ v = ((v * m) ^ r) & mask;
+ v = ((v << 1) & mask) | (v >> (masklen - 1));
+ }
+
+ /* Random multiply (by an odd number), XOR and rotate of upper half */
+ m = (pg_prng_uint64(&state) & mask) | 1;
+ r = pg_prng_uint64(&state) & mask;
+ t = size - 1 - v;
+ if (t <= mask)
+ {
+ t = ((t * m) ^ r) & mask;
+ t = ((t << 1) & mask) | (t >> (masklen - 1));
+ v = size - 1 - t;
+ }
+
+ /* Random offset */
+ r = pg_prng_uint64_range(&state, 0, size - 1);
+ v = (v + r) % size;
+ }
+
+ return (int64) v;
+}
diff --git a/src/include/common/pgbench_funcs.h b/src/include/common/pgbench_funcs.h
new file mode 100644
index 00000000000..62095d93410
--- /dev/null
+++ b/src/include/common/pgbench_funcs.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgbench_funcs.h
+ * Shared random distribution, permutation, and hashing functions for
+ * pgbench and backend extensions.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/pgbench_funcs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PGBENCH_FUNCS_H
+#define PGBENCH_FUNCS_H
+
+#include "common/pg_prng.h"
+
+/* Parameter boundaries for statistical distributions */
+#define PGBENCH_MIN_GAUSSIAN_PARAM 2.0
+#define PGBENCH_MIN_ZIPFIAN_PARAM 1.001
+#define PGBENCH_MAX_ZIPFIAN_PARAM 1000.0
+
+/* Hashing Constants */
+#define PGBENCH_FNV_PRIME UINT64CONST(0x100000001b3)
+#define PGBENCH_FNV_OFFSET_BASIS UINT64CONST(0xcbf29ce484222325)
+#define PGBENCH_MM2_MUL UINT64CONST(0xc6a4a7935bd1e995)
+#define PGBENCH_MM2_MUL_TIMES_8 UINT64CONST(0x35253c9ade8f4ca8)
+#define PGBENCH_MM2_ROT 47
+
+/* Random Distribution Functions */
+extern int64 pgbench_random(pg_prng_state *state, int64 min, int64 max);
+extern int64 pgbench_random_gaussian(pg_prng_state *state, int64 min, int64 max,
+ double parameter);
+extern int64 pgbench_random_exponential(pg_prng_state *state, int64 min, int64 max,
+ double parameter);
+extern int64 pgbench_random_zipfian(pg_prng_state *state, int64 min, int64 max,
+ double s);
+extern int64 pgbench_random_poisson(pg_prng_state *state, double center);
+
+/* Hashing Functions */
+extern int64 pgbench_hash_fnv1a(int64 val, uint64 seed);
+extern int64 pgbench_hash_murmur2(int64 val, uint64 seed);
+
+/* Permutation Function */
+extern int64 pgbench_permute(int64 val, int64 isize, int64 seed);
+
+#endif /* PGBENCH_FUNCS_H */
--
2.55.0.897.gb25b4bd76c-goog
# pgbench Modular Refactoring Architecture & Implementation Plan
> [!NOTE]
> **Context**: Refactoring `src/bin/pgbench/pgbench.c` (~7,700 lines) into a modular, maintainable, and extensible subsystem architecture in PostgreSQL `master`.
---
## 1. Architectural Vision & Design Decisions
### 1.1 Key Architectural Decisions (from Alignment)
1. **Subsystem Organization**: Flat structure in `src/bin/pgbench/` adhering to core PostgreSQL `src/bin/` conventions.
2. **State Management**: Explicit context objects (`PgBenchConfig`, `EngineContext`, `TState`, `CState`) passed to subsystem APIs instead of scattered file-scope globals.
3. **Command Execution**: Pluggable `CommandHandler` dispatch table with uniform function signatures and structured `CommandResult` return statuses.
4. **Variable Model**: Scoped Stack Frame Variable Store allowing nested variable lexical scopes for loops (`\for`, `\while`), transaction blocks, and procedures.
5. **Event Loop Multiplexing**: Abstracted `PgBenchPoller` / `SocketSet` interface encapsulating OS-specific multiplexers (`ppoll`, `poll`, `select`, Windows `WaitForMultipleObjects`).
6. **Concurrency Model**: Strict lockless per-thread execution (`TState` owns sockets, clients, and stats; thread-safe aggregation at reporting boundaries only).
7. **Migration Strategy**: 7-phase iterative refactoring with independently verifiable, bisectable commits.
---
## 2. Target Subsystem Breakdown
```
src/bin/pgbench/
âââ pgbench.c # Main CLI entrypoint, option parsing, runner orchestration (~400 LOC)
âââ pgbench.h # Public core types and subsystem interfaces
âââ context.h # Global configuration, engine context, thread & client state structs
â
âââ stats.h / stats.c # Latency tracking, statistics aggregation, out-of-band transaction logs
âââ poller.h / poller.c# Unified cross-platform socket multiplexing (ppoll, poll, select, Win32)
âââ variable.h / variable.c # Scoped Stack Frame Variable Store, lookup, creation, typing
âââ script.h / script.c# Script loader, exprparse/exprscan integration, AST evaluator
âââ commands.h / commands.c # Pluggable meta-command registry and handlers (\set, \sleep, \gset, etc.)
âââ engine.h / engine.c# Client connection state machine, pipeline coordination, worker thread loop
âââ init.h / init.c # Schema creation, synthetic data generation, table partitioning, vacuuming
```
---
## 3. Subsystem Architecture Specifications
```
âââââââââââââââââââââââââââââââââ
â pgbench.c â
â (CLI, Main, Config) â
âââââââââââââââââ¬ââââââââââââââââ
â
â¼
âââââââââââââââââââââââââââââââââ
â context.h â
â (PgBenchConfig, TState, CState)
âââââââââââââââââ¬ââââââââââââââââ
â
âââââââââââââââââââââââââââââââ¼ââââââââââââââââââââââââââââââ
â â â
â¼ â¼ â¼
âââââââââââââââââââââ âââââââââââââââââââââ âââââââââââââââââââââ
â init.c/h â â engine.c/h â â stats.c/h â
â (Schema & Data â â (Connection State â â (Latencies, Logs, â
â Generation -i) â â Machine & Loop) â â Aggregations) â
âââââââââââââââââââââ âââââââââââ¬ââââââââââ âââââââââââââââââââââ
â
âââââââââââââââââââââââââââââââ¼ââââââââââââââââââââââââââââââ
â â â
â¼ â¼ â¼
âââââââââââââââââââââ âââââââââââââââââââââ âââââââââââââââââââââ
â poller.c/h â â commands.c/h â â variable.c/h â
â (Socket Event â â (Pluggable Meta- â â (Scoped Stack â
â Multiplexing) â â Command Registry)â â Variable Store) â
âââââââââââââââââââââ âââââââââââ¬ââââââââââ âââââââââââââââââââââ
â
â¼
âââââââââââââââââââââ
â script.c/h â
â (AST & Expression â
â Evaluation) â
âââââââââââââââââââââ
```
### 3.1 Context & Configuration (`context.h`)
Encapsulates runtime state, eliminating global variables:
* `PgBenchConfig`: CLI options (scale, duration, tx count, throttle delay, sampling rates, latency limits, debug level).
* `PgBenchContext`: Global benchmark instance state, script descriptors, target weights, shared memory / sync barriers.
* `TState` (Thread Context): Per-thread state, owned client array, local PRNG sequences, local `PgBenchPoller`.
* `CState` (Client Context): Client connection, transaction state, current script AST pointer, local `VariableScopeStack`, pipeline state.
### 3.2 Scoped Variable Store (`variable.h` / `variable.c`)
Replaces flat sorted dynamic array with a scoped stack model:
```c
typedef struct VariableScope
{
struct VariableScope *parent; /* Enclosing scope (NULL for global client scope) */
int nvariables;
int alloc;
Variable *vars; /* Sorted array of variables within this scope */
} VariableScope;
typedef struct VariableScopeStack
{
VariableScope *current; /* Top of scope stack */
} VariableScopeStack;
/* API */
bool var_scope_push(VariableScopeStack *stack);
bool var_scope_pop(VariableScopeStack *stack);
bool var_put_value(VariableScopeStack *stack, const char *name, const PgBenchValue *val, bool local_only);
bool var_get_value(VariableScopeStack *stack, const char *name, PgBenchValue *val);
```
* **Benefits**: Instant support for loop variables (`\for i 1 100`), local variables in stored procedures or script blocks, avoiding variable pollution.
### 3.3 Pluggable Command Handler Registry (`commands.h` / `commands.c`)
Replaces hardcoded `switch(cmd->type)` in state machines:
```c
typedef enum CommandStatus
{
CMD_OK, /* Command completed successfully, advance to next command */
CMD_YIELD, /* Yield execution back to event loop (e.g. waiting on async socket) */
CMD_SLEEP, /* Client scheduled for sleep */
CMD_BRANCH, /* Conditional or loop jump: pc modified in CState */
CMD_ERROR /* Runtime execution error */
} CommandStatus;
typedef struct CommandResult
{
CommandStatus status;
int64 sleep_us;
char *error_msg;
} CommandResult;
typedef CommandResult (*CommandHandler)(CState *st, ParsedCommand *cmd);
typedef struct CommandDescriptor
{
const char *name;
CommandType type;
CommandHandler handler;
} CommandDescriptor;
```
* **Command Table**:
* `\set` $\to$ `handle_cmd_set()`
* `\sleep` $\to$ `handle_cmd_sleep()`
* `\shell` $\to$ `handle_cmd_shell()`
* `\gset` / `\cset` $\to$ `handle_cmd_gset()`
* `\startpipeline` / `\endpipeline` $\to$ `handle_cmd_pipeline()`
* `\if` / `\elif` / `\else` / `\endif` $\to$ `handle_cmd_conditional()`
* *(Future Extension Point)*: `\for`, `\while`, `\try`, `\catch` register seamlessly without touching `engine.c`.
### 3.4 Socket Event Poller (`poller.h` / `poller.c`)
Abstracts multi-platform socket multiplexing:
```c
typedef struct PgBenchPoller PgBenchPoller;
PgBenchPoller *poller_create(int max_sockets);
void poller_destroy(PgBenchPoller *poller);
bool poller_add_socket(PgBenchPoller *poller, int fd, int events, void *user_data);
bool poller_modify_socket(PgBenchPoller *poller, int fd, int events);
bool poller_remove_socket(PgBenchPoller *poller, int fd);
int poller_wait(PgBenchPoller *poller, int64 timeout_us, PollerEvent *events_out, int max_events);
```
### 3.5 Script AST & Expression Engine (`script.h` / `script.c`)
Encapsulates `exprparse.y`, Bison/Flex generation, script tokenization, and expression evaluation:
* `script_load_file()`, `script_load_string()`, `script_free()`
* `script_eval_expr(CState *st, PgBenchExpr *expr, PgBenchValue *out_val, char **err_msg)`
### 3.6 Connection State Machine & Engine (`engine.h` / `engine.c`)
Focuses solely on connection lifecycle and transaction flow:
* States: `CSTATE_START_TX`, `CSTATE_SEND_QUERY`, `CSTATE_WAIT_RESULT`, `CSTATE_SLEEP`, `CSTATE_FINISHED`, etc.
* `engine_step_client(CState *st)`
* `engine_thread_loop(TState *thread)`
---
## 4. Phase-by-Phase Implementation Plan
```mermaid
flowchart LR
P1["Phase 1: stats.c/h"] --> P2["Phase 2: variable.c/h"]
P2 --> P3["Phase 3: poller.c/h"]
P3 --> P4["Phase 4: script.c/h"]
P4 --> P5["Phase 5: commands.c/h"]
P5 --> P6["Phase 6: init.c/h"]
P6 --> P7["Phase 7: engine.c/h & pgbench.c"]
```
| Phase | Subsystem | Extracted Elements | Verification Gate |
| :--- | :--- | :--- | :--- |
| **Phase 1** | `stats.c` / `stats.h` | `SimpleStats`, `StatsData`, `accumStats()`, `mergeSimpleStats()`, out-of-band transaction logs, progress printers | `make check` (TAP: 681 tests pass) |
| **Phase 2** | `variable.c` / `variable.h` | `Variable`, `lookupCreateVariable()`, `putVariable()`, `getVariableValue()`, `VariableScopeStack` | `make check` (TAP: 681 tests pass) |
| **Phase 3** | `poller.c` / `poller.h` | `ppoll`, `poll`, `select`, Windows socket sets, event dispatch abstraction | `make check` (TAP: 681 tests pass) |
| **Phase 4** | `script.c` / `script.h` | Script loader, parser wrappers, `evalStandardFunc()`, `evalLazyFunc()`, `evaluateExpr()`, `EvalResult` | `make check` (TAP: 681 tests pass) |
| **Phase 5** | `commands.c` / `commands.h` | Pluggable `CommandHandler` table, handlers for `\set`, `\sleep`, `\shell`, `\gset`, `\startpipeline`, `\if` | `make check` (TAP: 681 tests pass) |
| **Phase 6** | `init.c` / `init.h` | `init()`, `initCreateTables()`, `initGenerateData()`, partitioning, foreign keys, vacuuming | `make check` (TAP: 681 tests pass) |
| **Phase 7** | `engine.c` / `engine.h` & `pgbench.c` | `advanceConnectionState()`, `threadRun()`, async client step loop; `pgbench.c` reduced to clean CLI driver (~400 lines) | Full TAP + Regression verification |
---
## 5. Benefits for Future Workloads (TPC-C, TPC-E, TPC-H)
1. **Effortless Addition of New Meta-Commands**: Adding `\for`, `\while`, `\try`, `\catch` requires only adding one `CommandHandler` function and an entry in `commands.c`.
2. **Heterogeneous Workload Groups (`--group`)**: Multiple client groups executing independent script streams can run on top of clean `EngineContext` / `TState` instances without global state conflicts.
3. **Driver / Stored-Procedure Benchmarks**: Client state machine is decoupled from SQL execution, making it trivial to plug in batch call drivers or stored procedure executors.
4. **Enhanced Diagnostic & Profiling Tools**: Clean `stats.c` and `poller.c` abstractions make integrating monotonic timing, micro-benchmarking, and custom Prometheus/Monarch metrics straightforward.