\set ECHO none
\pset format unaligned

-- ----------------------------------------------------------------------
-- Pin the BID (repair_key / gate_mulinput) arm of the marginal-vector
-- pre-pass (src/AggMarginalEvaluator.cpp).  A HAVING COUNT / SUM / AVG over
-- a mutually-exclusive block is exact: each block is a categorical (at most
-- one alternative present, Σp_i ≤ 1, the null arm contributing 0), and
-- blocks are independent, so the engine convolves them with the TID part.
-- off (cmp_probability_evaluation off) is the exact enumerator, on is the
-- engine; they must agree to four decimals, and on must actually FIRE (the
-- value-0-present vs empty-group distinction is the having_sum_zero corner).
-- ----------------------------------------------------------------------

CREATE FUNCTION bid_parity(opname text, q text)
  RETURNS TABLE(shape text, p_off numeric, p_on numeric, diff numeric) AS $$
DECLARE tok uuid; off_p numeric; on_p numeric;
BEGIN
  EXECUTE q INTO tok;                         -- circuit token (GUC-independent)
  EXECUTE 'SET provsql.cmp_probability_evaluation = off';
  off_p := round(probability_evaluate(tok)::numeric, 4);
  EXECUTE 'SET provsql.cmp_probability_evaluation = on';
  on_p := round(probability_evaluate(tok)::numeric, 4);
  RETURN QUERY SELECT opname, off_p, on_p, round(abs(off_p - on_p), 6);
END $$ LANGUAGE plpgsql;

-- A single block X with P(X=0)=0.5, P(X=10)=0.5 (Σp=1, never empty).
CREATE TABLE bid_z(d text, val int, p float);
INSERT INTO bid_z VALUES ('d', 0, 0.5), ('d', 10, 0.5);
SELECT repair_key('bid_z', 'd');
DO $$ BEGIN PERFORM set_prob(provenance(), p) FROM bid_z; END $$;

SELECT * FROM bid_parity('z SUM < 5',  'SELECT provenance() FROM bid_z GROUP BY d HAVING sum(val) < 5');
SELECT * FROM bid_parity('z SUM = 0',  'SELECT provenance() FROM bid_z GROUP BY d HAVING sum(val) = 0');
SELECT * FROM bid_parity('z SUM >= 5', 'SELECT provenance() FROM bid_z GROUP BY d HAVING sum(val) >= 5');
SELECT * FROM bid_parity('z AVG < 5',  'SELECT provenance() FROM bid_z GROUP BY d HAVING avg(val) < 5');
SELECT * FROM bid_parity('z COUNT < 2','SELECT provenance() FROM bid_z GROUP BY d HAVING count(*) < 2');
-- MIN / MAX over a BID block: each block is an independent categorical, so a
-- value-thresholded subset is all-absent w.p. 1-Σp over its matching
-- alternatives; the engine fires and matches the enumerator.
SELECT * FROM bid_parity('z MIN < 5',   'SELECT provenance() FROM bid_z GROUP BY d HAVING min(val) < 5');
SELECT * FROM bid_parity('z MAX >= 10', 'SELECT provenance() FROM bid_z GROUP BY d HAVING max(val) >= 10');
SELECT * FROM bid_parity('z MAX = 0',   'SELECT provenance() FROM bid_z GROUP BY d HAVING max(val) = 0');

-- A block with a null outcome: P(=0)=0.3, P(=10)=0.3, P(empty)=0.4.
CREATE TABLE bid_n(e text, val int, p float);
INSERT INTO bid_n VALUES ('e', 0, 0.3), ('e', 10, 0.3);
SELECT repair_key('bid_n', 'e');
DO $$ BEGIN PERFORM set_prob(provenance(), p) FROM bid_n; END $$;

-- present-and-0 (0.3); the empty world (0.4) is excluded though its sum is 0.
SELECT * FROM bid_parity('n SUM < 5',   'SELECT provenance() FROM bid_n GROUP BY e HAVING sum(val) < 5');
SELECT * FROM bid_parity('n COUNT >= 1','SELECT provenance() FROM bid_n GROUP BY e HAVING count(*) >= 1');

-- Two independent blocks in one group: X in {0,10}, Y in {0,1}.
CREATE TABLE bid_xy(blk text, c int, val int, p float);
INSERT INTO bid_xy VALUES ('X',1,0,0.5),('X',1,10,0.5),('Y',1,0,0.5),('Y',1,1,0.5);
SELECT repair_key('bid_xy', 'blk');
DO $$ BEGIN PERFORM set_prob(provenance(), p) FROM bid_xy; END $$;

SELECT * FROM bid_parity('xy SUM < 11', 'SELECT provenance() FROM bid_xy GROUP BY c HAVING sum(val) < 11');
SELECT * FROM bid_parity('xy SUM <= 1', 'SELECT provenance() FROM bid_xy GROUP BY c HAVING sum(val) <= 1');
SELECT * FROM bid_parity('xy MAX >= 10','SELECT provenance() FROM bid_xy GROUP BY c HAVING max(val) >= 10');
SELECT * FROM bid_parity('xy MIN >= 1', 'SELECT provenance() FROM bid_xy GROUP BY c HAVING min(val) >= 1');

-- Confirm the BID arm actually FIRES (pin the "safe-join aggregate" NOTICE):
-- once for a single block, once for the two-block convolution.
SET provsql.verbose_level = 5;
SELECT provenance() AS t1 FROM bid_z  GROUP BY d HAVING sum(val) < 5  \gset
SELECT round(probability_evaluate(:'t1'::uuid)::numeric, 4) AS z_fires;
SELECT provenance() AS t2 FROM bid_xy GROUP BY c HAVING sum(val) < 11 \gset
SELECT round(probability_evaluate(:'t2'::uuid)::numeric, 4) AS xy_fires;
SELECT provenance() AS t3 FROM bid_z  GROUP BY d HAVING max(val) >= 10 \gset
SELECT round(probability_evaluate(:'t3'::uuid)::numeric, 4) AS max_fires;
SET provsql.verbose_level = 0;

DROP FUNCTION bid_parity(text, text);
SELECT remove_provenance('bid_z');
SELECT remove_provenance('bid_n');
SELECT remove_provenance('bid_xy');
DROP TABLE bid_z;
DROP TABLE bid_n;
DROP TABLE bid_xy;
