\set ECHO none \pset format unaligned SET search_path TO provsql_test,provsql; -- Constructors create the right gate types and serialise their -- distribution parameters into the gate's extra byte string. SELECT get_gate_type((provsql.normal(2.5, 0.5))::uuid) AS rv_normal_kind; SELECT get_extra((provsql.normal(2.5, 0.5))::uuid) AS rv_normal_extra; SELECT get_gate_type((provsql.uniform(1, 3))::uuid) AS rv_uniform_kind; SELECT get_extra((provsql.uniform(1, 3))::uuid) AS rv_uniform_extra; SELECT get_gate_type((provsql.exponential(0.7))::uuid) AS rv_exp_kind; SELECT get_extra((provsql.exponential(0.7))::uuid) AS rv_exp_extra; SELECT get_gate_type((provsql.erlang(3, 0.5))::uuid) AS rv_erlang_kind; SELECT get_extra((provsql.erlang(3, 0.5))::uuid) AS rv_erlang_extra; -- as_random creates a gate_value (constant), not a gate_rv. SELECT get_gate_type((provsql.as_random(42))::uuid) AS const_kind; SELECT get_extra((provsql.as_random(42))::uuid) AS const_extra; -- Text IO round-trips: reparsing the printed form yields the same -- struct. A random_variable is a thin UUID wrapper, so the text -- form is the bare hyphenated UUID and equality on the UUID is -- sufficient to certify the round-trip. WITH r AS (SELECT provsql.as_random(3.14) AS v) SELECT (v::text::random_variable)::uuid = (v)::uuid AS uuid_roundtrip FROM r; -- Implicit cast random_variable -> uuid yields the same UUID as the -- explicit ()::uuid function. WITH r AS (SELECT provsql.normal(0, 1) AS v) SELECT v::uuid = (v)::uuid AS implicit_cast_to_uuid FROM r; -- A column of a real table can hold random_variable values and survive -- through the pg_dump-shaped text representation back into a row. CREATE TABLE sensors(id text, reading provsql.random_variable); INSERT INTO sensors VALUES ('s1', provsql.normal(2.5, 0.5)), ('s2', provsql.uniform(1, 3)), ('s3', provsql.exponential(0.7)), ('s4', provsql.as_random(2)); SELECT id, get_gate_type(reading::uuid) AS gate_type FROM sensors ORDER BY id; SELECT id, get_extra(reading::uuid) AS extra FROM sensors ORDER BY id; DROP TABLE sensors; -- as_random is IMMUTABLE with a deterministic UUID derived from the -- constant: two calls for the same value resolve to the same gate. SELECT (provsql.as_random(2))::uuid = (provsql.as_random(2))::uuid AS as_random_deterministic; -- Different constants give different UUIDs. SELECT (provsql.as_random(2))::uuid <> (provsql.as_random(3))::uuid AS as_random_distinct; -- Implicit cast double precision -> random_variable: a float8 literal -- in a random_variable context is auto-lifted to the same gate -- as_random would produce. SELECT (2.5::double precision::random_variable)::uuid = (provsql.as_random(2.5::double precision))::uuid AS implicit_cast_dedup_float8; -- Direct integer cast (PG operator resolution does not chain casts -- across multiple steps, so int -> random_variable is registered as -- its own cast via an as_random(integer) overload). SELECT (7::integer::random_variable)::uuid = (provsql.as_random(7::integer))::uuid AS int_literal_via_cast; -- Direct numeric cast for "2.5"-style literals (PG's default literal -- type for unquoted decimals is numeric). SELECT (2.5::numeric::random_variable)::uuid = (provsql.as_random(2.5::numeric))::uuid AS numeric_literal_via_cast; -- The continuous-distribution constructors are VOLATILE so that each -- call mints a FRESH uuid_generate_v4(): two calls to normal(0, 1) in -- the same query must produce *independent* random variables. If -- anyone weakens these to STABLE or IMMUTABLE, PostgreSQL would fold -- the call and the resulting circuit would silently collapse the two -- RVs into one shared gate, breaking the c-table model. These tests -- pin the contract. SELECT (provsql.normal(0, 1))::uuid <> (provsql.normal(0, 1))::uuid AS normal_calls_independent; SELECT (provsql.uniform(0, 1))::uuid <> (provsql.uniform(0, 1))::uuid AS uniform_calls_independent; SELECT (provsql.exponential(1))::uuid <> (provsql.exponential(1))::uuid AS exponential_calls_independent; SELECT (provsql.erlang(3, 1))::uuid <> (provsql.erlang(3, 1))::uuid AS erlang_calls_independent; -- Erlang(1, λ) is exactly Exp(λ): the constructor silently routes -- through exponential so the gate's extra reflects the underlying -- exponential form, sharing the entire downstream sampler / analytic -- path with vanilla Exp(λ). SELECT get_extra((provsql.erlang(1, 0.7))::uuid) AS erlang_one_routes_to_exp; -- Degenerate distributions: silently routed through as_random so the -- resulting gate is a gate_value, sharing its UUID with as_random(x). SELECT get_gate_type((provsql.normal(5, 0))::uuid) AS normal_zero_sigma_kind; SELECT (provsql.normal(5, 0))::uuid = (provsql.as_random(5))::uuid AS normal_zero_sigma_dedups_with_as_random; SELECT get_gate_type((provsql.uniform(7, 7))::uuid) AS uniform_degenerate_kind; SELECT (provsql.uniform(7, 7))::uuid = (provsql.as_random(7))::uuid AS uniform_degenerate_dedups_with_as_random; -- Rejected parameters. Use VERBOSITY terse to keep error output -- compact; restore default afterwards so other tests in the suite are -- unaffected if they share the connection. \set VERBOSITY terse SELECT provsql.normal('NaN'::float8, 1); SELECT provsql.normal(0, 'Infinity'::float8); SELECT provsql.normal(0, -1); SELECT provsql.uniform('NaN'::float8, 1); SELECT provsql.uniform(3, 1); SELECT provsql.exponential('Infinity'::float8); SELECT provsql.exponential(0); SELECT provsql.exponential(-0.5); SELECT provsql.erlang(0, 1); SELECT provsql.erlang(-2, 1); SELECT provsql.erlang(3, 'NaN'::float8); SELECT provsql.erlang(3, 0); SELECT provsql.erlang(3, -0.5); \set VERBOSITY default -- as_random allows non-finite floats: NaN and ±Infinity are valid -- float8 values with well-defined IEEE 754 comparison semantics. -- Each routes through the value-gate extra (where the literal text -- is preserved verbatim) and produces a distinct gate. SELECT get_extra((provsql.as_random('NaN'::float8))::uuid) AS as_random_nan_extra; SELECT get_extra((provsql.as_random('Infinity'::float8))::uuid) AS as_random_pos_inf_extra; SELECT get_extra((provsql.as_random('-Infinity'::float8))::uuid) AS as_random_neg_inf_extra; WITH r AS (SELECT provsql.as_random('NaN'::float8) AS v) SELECT (v::text::random_variable)::uuid = (v)::uuid AS nan_text_roundtrip FROM r; -- The three special values produce three distinct gates. SELECT count(DISTINCT u) AS distinct_special_uuids FROM (VALUES ((provsql.as_random('NaN'::float8))::uuid), ((provsql.as_random('Infinity'::float8))::uuid), ((provsql.as_random('-Infinity'::float8))::uuid) ) AS t(u); -- IEEE 754 has two zeros (-0.0 and +0.0) but they denote the same -- constant. as_random canonicalises -0.0 to +0.0 so both yield the -- same gate, and uniform(-0, +0) (which routes through as_random) -- inherits the canonicalisation. SELECT (provsql.as_random((-0)::float8))::uuid = (provsql.as_random((0)::float8))::uuid AS as_random_signed_zero_dedup; SELECT (provsql.uniform((-0)::float8, (0)::float8))::uuid = (provsql.as_random((0)::float8))::uuid AS uniform_signed_zero_dedup;