From 31e8e96c92ea89655137083e71176c01ae8dc445 Mon Sep 17 00:00:00 2001
From: Hannu Krosing <hannuk@google.com>
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

