\set ECHO none
\pset format unaligned

-- Safe-query rewriter -- ancestry-based disjointness gate.
--
-- The candidate gate refuses any query whose FROM-list entries have
-- registered ancestor sets that overlap (the gate-level atoms would
-- not be independent across the join even when the syntactic relid
-- check accepts).  Same-relid pairs are still handled by the
-- existing shared-relid bail and its PK / disjoint-constant
-- approvals; the ancestry check fires on DIFFERENT-relid pairs whose
-- lineage traces back to a common base table.

CREATE TABLE sad_base(x int);
INSERT INTO sad_base VALUES (1), (1), (2);
SELECT add_provenance('sad_base');

CREATE TABLE sad_other(x int);
INSERT INTO sad_other VALUES (1), (2), (2);
SELECT add_provenance('sad_other');

DO $$ BEGIN
  PERFORM set_prob(provsql, 0.5) FROM sad_base;
  PERFORM set_prob(provsql, 0.4) FROM sad_other;
END $$;

-- The CTAS lineage hook records ancestry on derived tables.
CREATE TABLE sad_derived_from_base  AS SELECT x, provsql FROM sad_base;
CREATE TABLE sad_derived_from_other AS SELECT x, provsql FROM sad_other;

-- Sanity: the derived tables inherited TID and the correct ancestors.
SELECT (get_table_info('sad_derived_from_base'::regclass::oid)).kind
         AS dfb_kind,
       get_ancestors('sad_derived_from_base'::regclass::oid)
         = ARRAY['sad_base'::regclass::oid]
         AS dfb_ancestors_correct;
SELECT (get_table_info('sad_derived_from_other'::regclass::oid)).kind
         AS dfo_kind,
       get_ancestors('sad_derived_from_other'::regclass::oid)
         = ARRAY['sad_other'::regclass::oid]
         AS dfo_ancestors_correct;

-- Probe helper: run the canonical 2-atom hierarchical CQ on (lhs, rhs)
-- under boolean_provenance=on and report whether the rewriter fired.
CREATE OR REPLACE FUNCTION sad_probe(lhs regclass, rhs regclass)
  RETURNS text AS $$
DECLARE
  gt   text;
BEGIN
  SET LOCAL provsql.provenance = 'boolean';
  EXECUTE format($f$
    CREATE TEMP TABLE sad_probe_res ON COMMIT DROP AS
      SELECT a.x AS x, provsql.provenance() AS p
        FROM %s a, %s b
       WHERE a.x = b.x
       GROUP BY a.x
  $f$, lhs, rhs);
  PERFORM provsql.remove_provenance('sad_probe_res');
  SELECT provsql.get_gate_type(p)::text INTO gt
    FROM sad_probe_res ORDER BY x LIMIT 1;
  DROP TABLE sad_probe_res;
  RETURN CASE WHEN gt = 'assumed' THEN 'fired' ELSE 'refused' END;
END;
$$ LANGUAGE plpgsql;

-- ---------------------------------------------------------------
-- (1) Derived table joined with one of its base ancestors:
--     ancestors {sad_base} overlap with {sad_base} -> REFUSE.
--     (The syntactic shared-relid bail does NOT catch this --
--     the two RTEs have different relids, sad_derived_from_base
--     and sad_base.)
-- ---------------------------------------------------------------
SELECT 'derived + its base' AS scenario,
       sad_probe('sad_derived_from_base', 'sad_base') AS result;

-- ---------------------------------------------------------------
-- (2) Two derived tables sharing an ancestor:
--     both came from sad_base -> ancestors {sad_base} vs {sad_base}
--     -> REFUSE.
-- ---------------------------------------------------------------
CREATE TABLE sad_derived_from_base_2 AS SELECT x, provsql FROM sad_base;
SELECT 'derived + derived (shared)' AS scenario,
       sad_probe('sad_derived_from_base', 'sad_derived_from_base_2') AS result;
DROP TABLE sad_derived_from_base_2;

-- ---------------------------------------------------------------
-- (3) Derived table joined with an unrelated base:
--     ancestors {sad_base} vs {sad_other} -> FIRE.
-- ---------------------------------------------------------------
SELECT 'derived + unrelated base' AS scenario,
       sad_probe('sad_derived_from_base', 'sad_other') AS result;

-- ---------------------------------------------------------------
-- (4) Two derived tables from disjoint bases:
--     ancestors {sad_base} vs {sad_other} -> FIRE.
-- ---------------------------------------------------------------
SELECT 'derived + derived (disjoint)' AS scenario,
       sad_probe('sad_derived_from_base', 'sad_derived_from_other') AS result;

-- ---------------------------------------------------------------
-- (5) Sanity: two unrelated bases still fire (ancestor sets
--     {sad_base} vs {sad_other} are trivially disjoint at the base
--     level too, so this is no regression).
-- ---------------------------------------------------------------
SELECT 'base + base (unrelated)' AS scenario,
       sad_probe('sad_base', 'sad_other') AS result;

DROP FUNCTION sad_probe(regclass, regclass);
DROP TABLE sad_derived_from_base;
DROP TABLE sad_derived_from_other;
DROP TABLE sad_base;
DROP TABLE sad_other;
