From 53c905816e20947d8ff0cd0c51e8b5690834255b Mon Sep 17 00:00:00 2001 From: Hannu Krosing 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 + +#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 extension_name; + @@ -166,6 +167,7 @@ CREATE EXTENSION extension_name; &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 @@ + 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 @@ + + + + pgbench — benchmark random distributions, hashing, and permutation functions + + + pgbench + extension + + + + The pgbench module provides SQL-callable functions + implementing the statistical random distribution generators, domain + permutations, and 64-bit hashing routines from the + benchmarking tool. + + + + These functions are installed into the pgbench schema and + enable reproducible server-side synthetic data generation, procedural + workload simulation, and client/server algorithmic parity in custom test + harnesses. + + + + This module is considered trusted, that is, it can be + installed by non-superusers who have CREATE privilege + on the current database. + + + + Functions + + + All functions provided by this extension are installed in the + pgbench schema. + + + + + + pgbench.setseed(seed double precision) returns void + + setseed + in pgbench + + + + pgbench.setseed(seed bigint) returns void + + + + Sets the seed for the backend's internal pseudorandom number generator used + by the pgbench random functions. When passed a + double precision value, it must be in the range + [-1.0, 1.0]. When passed a bigint value, + it initializes the full 64-bit state directly. + + + + + + + pgbench.random(min bigint, max bigint) returns bigint + + random + in pgbench + + + + + Generates a uniformly-distributed random integer between + min and max, inclusive. + + + + + + + pgbench.random_gaussian(min bigint, max bigint, parameter double precision) returns bigint + + random_gaussian + in pgbench + + + + + Generates a Gaussian-distributed random integer between + min and max, inclusive. + The parameter defines how concentrated the values + are toward the center of the range and must be at least 2.0. + + + + + + + pgbench.random_exponential(min bigint, max bigint, parameter double precision) returns bigint + + random_exponential + in pgbench + + + + + Generates an exponentially-distributed random integer between + min and max, inclusive. + The parameter controls the distribution density and + must be greater than 0.0. + + + + + + + pgbench.random_zipfian(min bigint, max bigint, parameter double precision) returns bigint + + random_zipfian + in pgbench + + + + + Generates a Zipfian-distributed random integer between + min and max, inclusive. + The parameter (Zipfian skew factor s) + must be in the range [1.001, 1000.0]. + + + + + + + pgbench.random_poisson(center double precision) returns bigint + + random_poisson + in pgbench + + + + + Generates a Poisson-distributed random integer centered on + center, which must be greater than 0.0. + + + + + + + pgbench.hash_murmur2(val bigint [, seed bigint ]) returns bigint + + hash_murmur2 + in pgbench + + + + + Computes the 64-bit Austin Appleby MurmurHash2 of val + using optional seed (default is 0). + + + + + + + pgbench.hash_fnv1a(val bigint [, seed bigint ]) returns bigint + + hash_fnv1a + in pgbench + + + + + Computes the 64-bit Fowler–Noll–Vo 1a hash of + val using optional seed + (default is 0). + + + + + + + pgbench.hash(val bigint [, seed bigint ]) returns bigint + + hash + in pgbench + + + + + An alias for pgbench.hash_murmur2. + + + + + + + pgbench.permute(val bigint, size bigint [, seed bigint ]) returns bigint + + permute + in pgbench + + + + + Permutes val into the range + [0, size) using a multi-round Feistel-style bijective + permutation seeded with seed (default is + 0). size must be greater than + 0. + + + + + + + + Examples + + + Generate a reproducible synthetic customer workload directly within SQL: + + + +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; + + + + -- 2.55.0.897.gb25b4bd76c-goog