\set ECHO none \pset format unaligned SET search_path TO provsql_test, provsql; -- Safe-query rewriter -- subquery-inlining pre-pass. PG inlines a -- view into the outer Query as an RTE_SUBQUERY before the planner -- hook runs; without the pre-pass the candidate gate refuses on the -- non-RTE_RELATION rtable entry. With the pre-pass the view body's -- RTEs are lifted into the outer rtable, the candidate gate sees a -- flat range table of base relations, and the existing hierarchical- -- CQ detector fires. -- -- Coverage: -- (V1) trivial @c SELECT @c * view. -- (V2) view + base-table join (the typical case). -- (V3) view body carrying its own WHERE conjunct (merged with the -- outer WHERE during inlining). -- (V4) projection with column rename. -- (V5) two-level nesting (view over a view), fixed-point loop. -- (V6) view that itself joins two tracked tables. -- (V7) negative: view body with GROUP BY (inlineable predicate -- rejects -> pre-pass no-op -> candidate gate refuses). -- (V8) negative: view + base table that share an underlying relid -- (shared-relid bail in candidate gate after inlining). CREATE TABLE vd_a(x int); CREATE TABLE vd_b(x int); CREATE TABLE vd_c(x int); INSERT INTO vd_a VALUES (1), (1), (2); INSERT INTO vd_b VALUES (1), (2), (2); INSERT INTO vd_c VALUES (1), (1), (2), (3); SELECT add_provenance('vd_a'); SELECT add_provenance('vd_b'); SELECT add_provenance('vd_c'); DO $$ BEGIN PERFORM set_prob(provsql, 0.5) FROM vd_a; PERFORM set_prob(provsql, 0.4) FROM vd_b; PERFORM set_prob(provsql, 0.6) FROM vd_c; END $$; -- Probe: given a query expression, return per-x whether the -- rewriter fired and the probability under the rewritten / baseline -- evaluators (rounded so floating-point noise doesn't drift the -- expected output). Mirrors the opq_probe idiom from -- safe_query_opaque.sql but with the additional probability cross- -- check. CREATE OR REPLACE FUNCTION vd_probe(qtxt text) RETURNS TABLE(x int, rewriter text, p_rew numeric, p_base numeric) AS $$ DECLARE rec record; BEGIN SET LOCAL provsql.boolean_provenance = on; EXECUTE format($f$ CREATE TEMP TABLE vd_rew ON COMMIT DROP AS SELECT q.x, provsql.provenance() AS p FROM (%s) q GROUP BY q.x $f$, qtxt); PERFORM provsql.remove_provenance('vd_rew'); SET LOCAL provsql.boolean_provenance = off; EXECUTE format($f$ CREATE TEMP TABLE vd_base ON COMMIT DROP AS SELECT q.x, provsql.provenance() AS p FROM (%s) q GROUP BY q.x $f$, qtxt); PERFORM provsql.remove_provenance('vd_base'); FOR rec IN SELECT r.x, CASE WHEN provsql.get_gate_type(r.p)::text = 'assumed_boolean' THEN 'fired' ELSE 'refused' END AS rewriter, ROUND(provsql.probability_evaluate(r.p)::numeric, 6) AS p_rew, ROUND(provsql.probability_evaluate(b.p)::numeric, 6) AS p_base FROM vd_rew r JOIN vd_base b ON r.x = b.x ORDER BY r.x LOOP x := rec.x; rewriter := rec.rewriter; p_rew := rec.p_rew; p_base := rec.p_base; RETURN NEXT; END LOOP; DROP TABLE vd_rew; DROP TABLE vd_base; END; $$ LANGUAGE plpgsql; -- --------------------------------------------------------------- -- (V1) Trivial SELECT * view -- nothing for the inliner to merge -- beyond the RTE itself. Rewriter sees a one-atom query, which is -- accepted by the deterministic-source branch of the detector. -- --------------------------------------------------------------- CREATE VIEW vd_v1 AS SELECT * FROM vd_a; SELECT 'V1' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v1 t, vd_b u WHERE t.x = u.x $$); DROP VIEW vd_v1; -- --------------------------------------------------------------- -- (V2) Standard 2-atom hierarchical CQ where one side comes -- through a view. -- --------------------------------------------------------------- CREATE VIEW vd_v2 AS SELECT * FROM vd_a; SELECT 'V2' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v2 t, vd_b u WHERE t.x = u.x $$); DROP VIEW vd_v2; -- --------------------------------------------------------------- -- (V3) View body carries its own WHERE; after inlining the predicate -- merges into the outer WHERE. The output row count for x=2 stays -- 1, the rewriter's per-x probability matches the baseline. -- --------------------------------------------------------------- CREATE VIEW vd_v3 AS SELECT * FROM vd_a WHERE x <= 2; SELECT 'V3' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v3 t, vd_b u WHERE t.x = u.x $$); DROP VIEW vd_v3; -- --------------------------------------------------------------- -- (V4) View renames its column. The inliner substitutes the outer -- Var with the TLE Var pointing at the underlying column; the -- rename is purely syntactic and disappears in the inlined query. -- --------------------------------------------------------------- CREATE VIEW vd_v4(renamed) AS SELECT a.x FROM vd_a a; SELECT 'V4' AS scenario, * FROM vd_probe( $$ SELECT t.renamed AS x FROM vd_v4 t, vd_b u WHERE t.renamed = u.x $$); DROP VIEW vd_v4; -- --------------------------------------------------------------- -- (V5) View over a view. The first iteration of the fixed-point -- loop inlines vd_v5_outer; the second iteration sees vd_v5_inner -- newly exposed in the fromlist and inlines it too. -- --------------------------------------------------------------- CREATE VIEW vd_v5_inner AS SELECT * FROM vd_a; CREATE VIEW vd_v5_outer AS SELECT * FROM vd_v5_inner; SELECT 'V5' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v5_outer t, vd_b u WHERE t.x = u.x $$); DROP VIEW vd_v5_outer; DROP VIEW vd_v5_inner; -- --------------------------------------------------------------- -- (V6) View body is itself a 2-atom join. After inlining, the -- outer query becomes a 3-atom hierarchical CQ. -- --------------------------------------------------------------- CREATE VIEW vd_v6 AS SELECT a.x FROM vd_a a, vd_b b WHERE a.x = b.x; SELECT 'V6' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v6 t, vd_c u WHERE t.x = u.x $$); DROP VIEW vd_v6; -- --------------------------------------------------------------- -- (V7) Negative: view body uses GROUP BY. The inlineable predicate -- refuses (the subquery's @c groupClause changes row lineage), the -- pre-pass returns NULL, and the candidate gate then refuses on the -- still-present RTE_SUBQUERY. -- --------------------------------------------------------------- CREATE VIEW vd_v7 AS SELECT a.x FROM vd_a a GROUP BY a.x; SELECT 'V7' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v7 t, vd_b u WHERE t.x = u.x $$); DROP VIEW vd_v7; -- --------------------------------------------------------------- -- (V8) Negative: view + base table that share an underlying relid. -- After inlining the outer rtable contains vd_a (from the view body) -- and vd_a (from the outer FROM); the candidate gate's shared-relid -- bail refuses. The PK / disjoint-constant self-join pre-passes -- don't apply (no PK on vd_a, no @c x @c = @c const conjuncts). -- --------------------------------------------------------------- CREATE VIEW vd_v8 AS SELECT * FROM vd_a; SELECT 'V8' AS scenario, * FROM vd_probe( $$ SELECT t.x FROM vd_v8 t, vd_a u WHERE t.x = u.x $$); DROP VIEW vd_v8; DROP FUNCTION vd_probe(text); DROP TABLE vd_a; DROP TABLE vd_b; DROP TABLE vd_c;