CREATE EXTENSION bloompg; SELECT bloompg_version() = 'bloompg 0.1.2 (PostgreSQL 18)' AS version_ok; SELECT current_setting('bloompg.enable') = 'on' AS enabled_by_default; SELECT current_setting('bloompg.sample_mode') = 'prepared' AS prepared_by_default; SELECT current_setting('bloompg.transfer_progress_metric') = 'ndv' AS transfer_progress_metric_default; SELECT current_setting('bloompg.sample_rate')::numeric = 0.01 AS intermediate_sample_rate_default; SELECT max_val::integer = 128 AS transfer_worker_limit_ok FROM pg_settings WHERE name = 'bloompg.transfer_workers'; CREATE SCHEMA bloompg_regress; SET search_path = bloompg_regress, public; SET max_parallel_workers_per_gather = 0; SET bloompg.sample_mode = 'instant'; SET bloompg.late_materialize = on; SET bloompg.late_materialize_min_width = '64B'; CREATE TABLE a(k integer, k2 integer, t text, d date, keep boolean, payload text); CREATE TABLE b(k bigint, k2 integer, t text, d date, payload text); CREATE TABLE c(k integer, k2 integer, flag boolean); INSERT INTO a SELECT CASE WHEN g % 97 = 0 THEN NULL ELSE g % 500 END, g % 17, 't' || (g % 29), date '2020-01-01' + (g % 180), g < 25 OR g % 211 = 0, repeat('a', 160 + g % 40) FROM generate_series(1, 2000) AS g; INSERT INTO b SELECT CASE WHEN g % 89 = 0 THEN NULL ELSE g % 650 END, g % 17, 't' || (g % 31), date '2020-01-01' + (g % 190), repeat('b', 140 + g % 30) FROM generate_series(1, 3000) AS g; INSERT INTO c SELECT CASE WHEN g % 101 = 0 THEN NULL ELSE g % 550 END, g % 17, g % 13 = 0 FROM generate_series(1, 2500) AS g; CREATE INDEX ON a(k); CREATE INDEX ON b(k); CREATE INDEX ON c(k); ANALYZE; CREATE TABLE pa(k integer, keep boolean) PARTITION BY RANGE (k); CREATE TABLE pa0 PARTITION OF pa FOR VALUES FROM (0) TO (250); CREATE TABLE pa1 PARTITION OF pa FOR VALUES FROM (250) TO (500); INSERT INTO pa SELECT g % 500, g % 199 = 0 FROM generate_series(1, 1800) AS g; ANALYZE pa; CREATE PROCEDURE assert_same(label text, query_text text) LANGUAGE plpgsql AS $$ DECLARE differences bigint; BEGIN PERFORM set_config('bloompg.enable', 'on', true); EXECUTE 'CREATE TEMP TABLE bloompg_on AS ' || query_text; PERFORM set_config('bloompg.enable', 'off', true); EXECUTE 'CREATE TEMP TABLE bloompg_off AS ' || query_text; EXECUTE 'SELECT count(*) FROM (' || '(SELECT * FROM bloompg_on EXCEPT ALL SELECT * FROM bloompg_off) ' || 'UNION ALL ' || '(SELECT * FROM bloompg_off EXCEPT ALL SELECT * FROM bloompg_on)' || ') AS delta' INTO differences; IF differences <> 0 THEN RAISE EXCEPTION '% differs by % rows', label, differences; END IF; DROP TABLE bloompg_on; DROP TABLE bloompg_off; END $$; CREATE PROCEDURE assert_plan_contains(label text, query_text text, needle text) LANGUAGE plpgsql AS $$ DECLARE plan_line text; found boolean := false; BEGIN PERFORM set_config('bloompg.enable', 'on', true); FOR plan_line IN EXECUTE 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) ' || query_text LOOP found := found OR position(needle IN plan_line) > 0; END LOOP; IF NOT found THEN RAISE EXCEPTION '% did not contain plan node %', label, needle; END IF; END $$; CREATE PROCEDURE assert_parallel_rescan_plan(label text, query_text text) LANGUAGE plpgsql AS $$ DECLARE plan_json jsonb; BEGIN PERFORM set_config('bloompg.enable', 'on', true); EXECUTE 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT JSON) ' || query_text INTO plan_json; IF NOT jsonb_path_exists(plan_json, ( '$.** ? (@."Node Type" == "Nested Loop")."Plans"[*] ? ' || '(@."Parent Relationship" == "Inner" && ' || '@."Node Type" == "Gather" && @."Actual Loops" > 1 && ' || '@."Workers Launched" > 0)')::jsonpath) THEN RAISE EXCEPTION '% did not rescan a worker-backed inner Gather', label; END IF; IF NOT jsonb_path_exists(plan_json, ( '$.** ? (@."Custom Plan Provider" == "BloomPG Memory" && ' || '@."Snapshot Reuse" == "parallel DSM (replicated)" && ' || '@."Actual Loops" > 1)')::jsonpath) THEN RAISE EXCEPTION '% did not rescan a replicated materialization', label; END IF; END $$; CREATE PROCEDURE assert_parameter_same(label text, bound_value integer) LANGUAGE plpgsql AS $$ DECLARE bloom_count bigint; native_count bigint; BEGIN PERFORM set_config('bloompg.enable', 'on', true); EXECUTE 'SELECT count(*) FROM a JOIN b ON a.k = b.k ' || 'WHERE a.keep AND a.k < $1' INTO bloom_count USING bound_value; PERFORM set_config('bloompg.enable', 'off', true); EXECUTE 'SELECT count(*) FROM a JOIN b ON a.k = b.k ' || 'WHERE a.keep AND a.k < $1' INTO native_count USING bound_value; IF bloom_count IS DISTINCT FROM native_count THEN RAISE EXCEPTION '% differs: bloom %, native %', label, bloom_count, native_count; END IF; END $$; CREATE PROCEDURE assert_scroll_cursor() LANGUAGE plpgsql AS $$ DECLARE cursor_handle refcursor; row_value record; bloom_first jsonb; bloom_last jsonb; bloom_backward jsonb; native_first jsonb; native_last jsonb; native_backward jsonb; query_text text := 'SELECT a.k AS ak, a.k2 AS ak2, b.k2 AS bk2 ' || 'FROM a JOIN b ON a.k = b.k WHERE a.keep ' || 'ORDER BY 1 NULLS LAST, 2, 3'; BEGIN PERFORM set_config('bloompg.enable', 'on', true); cursor_handle := 'bloom_scroll'; OPEN cursor_handle SCROLL FOR EXECUTE query_text; FETCH FIRST FROM cursor_handle INTO row_value; bloom_first := to_jsonb(row_value); FETCH LAST FROM cursor_handle INTO row_value; bloom_last := to_jsonb(row_value); MOVE BACKWARD 7 FROM cursor_handle; FETCH NEXT FROM cursor_handle INTO row_value; bloom_backward := to_jsonb(row_value); CLOSE cursor_handle; PERFORM set_config('bloompg.enable', 'off', true); cursor_handle := 'native_scroll'; OPEN cursor_handle SCROLL FOR EXECUTE query_text; FETCH FIRST FROM cursor_handle INTO row_value; native_first := to_jsonb(row_value); FETCH LAST FROM cursor_handle INTO row_value; native_last := to_jsonb(row_value); MOVE BACKWARD 7 FROM cursor_handle; FETCH NEXT FROM cursor_handle INTO row_value; native_backward := to_jsonb(row_value); CLOSE cursor_handle; IF bloom_first IS DISTINCT FROM native_first OR bloom_last IS DISTINCT FROM native_last OR bloom_backward IS DISTINCT FROM native_backward THEN RAISE EXCEPTION 'scroll cursor results differ'; END IF; END $$; CREATE PROCEDURE run_differential_matrix() LANGUAGE plpgsql AS $$ DECLARE i integer; query_text text; BEGIN FOR i IN 0..59 LOOP CASE i % 5 WHEN 0 THEN query_text := format( 'SELECT a.k2, count(*), sum(b.k2) ' || 'FROM a JOIN b ON a.k = b.k ' || 'WHERE a.k2 <= %s AND b.k2 >= %s ' || 'AND (a.keep OR a.k < %s) GROUP BY a.k2', 2 + i % 15, i % 8, 10 + i % 90); WHEN 1 THEN query_text := format( 'SELECT a.k, count(*) FROM a JOIN b ' || 'ON a.k = b.k AND a.k2 = b.k2 ' || 'WHERE a.keep AND a.k < %s GROUP BY a.k', 30 + (i * 17) % 470); WHEN 2 THEN query_text := format( 'SELECT count(*), sum(a.k2) FROM a ' || 'JOIN b ON a.k = b.k JOIN c ON b.k = c.k ' || 'WHERE a.k2 < %s AND b.k2 >= %s AND c.flag', 1 + i % 17, i % 12); WHEN 3 THEN query_text := format( 'SELECT a.k2, count(b.k) FROM a LEFT JOIN b ON a.k = b.k ' || 'WHERE a.keep AND a.k2 <= %s GROUP BY a.k2', i % 17); ELSE query_text := format( 'SELECT a.k, a.k2 FROM a WHERE a.keep AND a.k < %s ' || 'AND %s (SELECT 1 FROM b WHERE b.k = a.k AND b.k2 = a.k2)', 20 + (i * 29) % 480, CASE WHEN i % 2 = 0 THEN 'EXISTS' ELSE 'NOT EXISTS' END); END CASE; CALL assert_same(format('matrix_%s', i), query_text); END LOOP; END $$; SET client_min_messages = warning; CALL assert_same('inner', 'SELECT a.k, a.k2, b.payload FROM a JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_same('composite', 'SELECT a.k, a.k2, count(*) FROM a JOIN b ON a.k = b.k AND a.k2 = b.k2 WHERE a.keep GROUP BY a.k, a.k2'); CALL assert_same('chain', 'SELECT count(*), sum(a.k2) FROM a JOIN b ON a.k = b.k JOIN c ON b.k = c.k WHERE a.keep AND c.flag'); CALL assert_same('text_and_date', 'SELECT a.t, count(*) FROM a JOIN b ON a.t COLLATE "C" = b.t COLLATE "C" AND a.d = b.d WHERE a.keep GROUP BY a.t'); CALL assert_same('left_outer', 'SELECT a.k AS ak, b.k AS bk, b.k2 FROM a LEFT JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_same('full_outer', 'SELECT coalesce(a.k, b.k::int) AS k, count(*) FROM a FULL JOIN b ON a.k = b.k WHERE a.keep IS TRUE OR b.k < 8 GROUP BY 1'); CALL assert_same('semi', 'SELECT a.k, a.k2 FROM a WHERE a.keep AND EXISTS (SELECT 1 FROM b WHERE b.k = a.k AND b.k2 = a.k2)'); CALL assert_same('anti', 'SELECT a.k, a.k2 FROM a WHERE a.keep AND NOT EXISTS (SELECT 1 FROM b WHERE b.k = a.k AND b.k2 = a.k2)'); CALL assert_same('self_join', 'SELECT x.k, count(*) FROM a x JOIN a y ON x.k = y.k WHERE x.keep AND y.k2 < 4 GROUP BY x.k'); CALL assert_same('materialized_cte', 'WITH x AS MATERIALIZED (SELECT k, k2, payload FROM b WHERE k < 300) SELECT count(*), sum(length(x.payload)) FROM a JOIN x ON a.k = x.k WHERE a.keep'); CALL assert_same('cte_aliases', 'WITH x AS MATERIALIZED (SELECT k, k2 FROM b WHERE k < 350) SELECT count(*) FROM a JOIN x x1 ON a.k = x1.k JOIN x x2 ON a.k = x2.k WHERE a.keep AND x1.k2 = x2.k2'); CALL assert_same('subquery_distinct', 'SELECT count(*) FROM a JOIN (SELECT DISTINCT k FROM b WHERE k < 400) s ON a.k = s.k WHERE a.keep'); CALL assert_same('subquery_group', 'SELECT sum(s.n) FROM a JOIN (SELECT k, count(*) n FROM b GROUP BY k) s ON a.k = s.k WHERE a.keep'); CALL assert_same('subquery_limit', 'SELECT count(*) FROM a JOIN (SELECT k, k2 FROM b ORDER BY k, k2 LIMIT 300 OFFSET 10) s ON a.k = s.k WHERE a.keep'); CALL assert_same('partition', 'SELECT pa.k, count(*) FROM pa JOIN b ON pa.k = b.k WHERE pa.keep GROUP BY pa.k'); CREATE VIEW a_view AS SELECT k, k2, keep, payload FROM a; CREATE MATERIALIZED VIEW b_mat AS SELECT k, k2, payload FROM b WHERE k < 500; ANALYZE b_mat; CALL assert_same('view', 'SELECT count(*) FROM a_view v JOIN b ON v.k = b.k WHERE v.keep'); CALL assert_plan_contains('view_plan', 'SELECT count(*) FROM a_view v JOIN b ON v.k = b.k WHERE v.keep', 'Custom Scan (BloomPG'); CALL assert_same('materialized_view', 'SELECT count(*) FROM a JOIN b_mat m ON a.k = m.k WHERE a.keep'); CALL assert_plan_contains('materialized_view_plan', 'SELECT count(*) FROM a JOIN b_mat m ON a.k = m.k WHERE a.keep', 'Custom Scan (BloomPG'); CALL assert_same('set_operation', 'SELECT k FROM a WHERE keep UNION ALL SELECT k::int FROM b WHERE k < 5'); CALL assert_parameter_same('parameter_10', 10); CALL assert_parameter_same('parameter_100', 100); CALL assert_parameter_same('parameter_1000', 1000); CALL assert_scroll_cursor(); SET enable_hashjoin = off; SET enable_mergejoin = off; CALL assert_same('nested_loop_execution', 'SELECT a.k, a.k2, b.payload FROM a JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_plan_contains('nested_loop_plan', 'SELECT count(*) FROM a JOIN b ON a.k = b.k WHERE a.keep', 'Nested Loop'); RESET enable_hashjoin; RESET enable_mergejoin; SET enable_hashjoin = off; SET enable_nestloop = off; CALL assert_same('merge_join_execution', 'SELECT a.k, a.k2, b.payload FROM a JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_plan_contains('merge_join_plan', 'SELECT count(*) FROM a JOIN b ON a.k = b.k WHERE a.keep', 'Merge Join'); RESET enable_hashjoin; RESET enable_nestloop; CALL run_differential_matrix(); SET bloompg.sample_mode = 'prepared'; CALL assert_same('prepared_sample', 'SELECT count(*) FROM a JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_same('prepared_subplan_qual', 'SELECT count(*) FROM a JOIN b ON a.k = b.k WHERE a.keep AND b.k NOT IN (SELECT c.k FROM c WHERE c.flag)'); SET bloompg.profile = on; SET bloompg.profile_log = off; CALL assert_same('prepared_initplan_qual', 'SELECT count(*) FROM a JOIN b ON a.k = b.k JOIN c ON b.k = c.k WHERE a.keep AND c.flag AND a.k2 <= (SELECT max(c2.k2) FROM c AS c2 WHERE c2.flag)'); SET bloompg.enable = on; SELECT count(*) > 0 AS prepared_initplan_query_ok FROM a JOIN b ON a.k = b.k JOIN c ON b.k = c.k WHERE a.keep AND c.flag AND a.k2 <= (SELECT max(c2.k2) FROM c AS c2 WHERE c2.flag); SELECT bloompg_last_profile()->>'status' = 'complete' AND (bloompg_last_profile()->'transfer'->'summary' ->>'applied')::boolean AND jsonb_array_length( bloompg_last_profile()->'transfer'->'steps') > 0 AS prepared_initplan_transfer_ok; SET bloompg.profile = off; RESET bloompg.profile_log; SELECT count(*) > 0 AS prepared_sample_query_ok FROM a JOIN b ON a.k = b.k WHERE a.keep; SELECT bloompg_last_trace() LIKE '%(cached)%' AS prepared_sample_cache_hit; SELECT bloompg_last_trace() LIKE '%samplemem=%cache=hit%' AS prepared_tuple_cache_hit; INSERT INTO a VALUES (777, 1, 'cached-dml', date '2024-01-01', true, repeat('x', 5000)); INSERT INTO b VALUES (777, 1, 'cached-dml', date '2024-01-01', repeat('y', 5000)); CALL assert_same('prepared_sample_after_dml', 'SELECT count(*), max(length(a.payload)) FROM a JOIN b ON a.k = b.k WHERE a.keep'); SET bloompg.sample_mode = 'instant'; SET bloompg.enable = on; SELECT count(*) > 0 AS cache_query_ok FROM a JOIN c AS c1 ON a.k = c1.k JOIN c AS c2 ON a.k = c2.k WHERE a.keep; SELECT bloompg_last_trace() LIKE '%cache=hit%' AS filter_cache_reuse_ok; -- Exact equality domains retain only NDV and a version. Once a relation has -- consumed the current version, an equal outgoing NDV proves that its key set -- is unchanged and redundant transfers around this triangle can be skipped. CREATE TABLE domain_seed(k integer, keep boolean); CREATE TABLE domain_left(k integer, payload integer); CREATE TABLE domain_right(k integer, payload integer); INSERT INTO domain_seed SELECT g, g <= 5 FROM generate_series(1, 1000) AS g; INSERT INTO domain_left SELECT 1 + (g % 200), g FROM generate_series(1, 2000) AS g; INSERT INTO domain_right SELECT 1 + (g % 200), g FROM generate_series(1, 2000) AS g; ANALYZE domain_seed, domain_left, domain_right; SET bloompg.excitation_threshold = 1.0; SET bloompg.profile = on; SET bloompg.profile_log = off; CALL assert_same('equality_domain_cycle', 'SELECT count(*) FROM domain_seed s JOIN domain_left l ON s.k = l.k JOIN domain_right r ON s.k = r.k AND l.k = r.k WHERE s.keep'); SET bloompg.enable = on; SELECT count(*) = 500 AS equality_domain_query_ok FROM domain_seed s JOIN domain_left l ON s.k = l.k JOIN domain_right r ON s.k = r.k AND l.k = r.k WHERE s.keep; SELECT COALESCE( bloompg_last_profile()->'transfer'->'summary'->>'progress_metric' = 'ndv' AND (bloompg_last_profile()->'transfer'->'summary' ->>'equality_domain_suppressed')::integer > 0 AND (bloompg_last_profile()->'transfer'->'summary' ->>'equality_domain_observations')::integer > 0 AND (bloompg_last_profile()->'transfer'->'summary' ->>'equality_domain_uncontained')::integer = 0, false) AS equality_domain_size_only_ok; -- Size containment is ambiguous if one relation contributes two attributes -- to the same equality class, so that shape must retain normal propagation. CREATE TABLE domain_multi(k1 integer, k2 integer, keep boolean); INSERT INTO domain_multi SELECT g, g, g <= 5 FROM generate_series(1, 1000) AS g; ANALYZE domain_multi; CALL assert_same('equality_domain_multi_column', 'SELECT count(*) FROM domain_multi m JOIN domain_left l ON m.k1 = l.k JOIN domain_right r ON m.k2 = r.k AND l.k = r.k WHERE m.keep'); SET bloompg.enable = on; SELECT count(*) = 500 AS equality_domain_multi_column_query_ok FROM domain_multi m JOIN domain_left l ON m.k1 = l.k JOIN domain_right r ON m.k2 = r.k AND l.k = r.k WHERE m.keep; SELECT COALESCE( (bloompg_last_profile()->'transfer'->'summary' ->>'equality_domain_classes')::integer = 0 AND (bloompg_last_profile()->'transfer'->'summary' ->>'equality_domain_suppressed')::integer = 0, false) AS equality_domain_multi_column_fallback_ok; SET bloompg.profile = off; RESET bloompg.profile_log; CREATE TABLE filter_src(k integer, keep boolean); CREATE TABLE filter_dst(k integer, payload text); INSERT INTO filter_src SELECT g, g <= 100 FROM generate_series(1, 1000) AS g; INSERT INTO filter_dst SELECT 1 + (g % 500), repeat('f', 96) FROM generate_series(1, 2000) AS g; ANALYZE filter_src, filter_dst; SET bloompg.excitation_threshold = 0.15; CALL assert_same('direct_filter', 'SELECT count(*), sum(length(d.payload)) FROM filter_src s JOIN filter_dst d ON s.k = d.k WHERE s.keep'); CALL assert_plan_contains('direct_filter_plan', 'SELECT count(*) FROM filter_src s JOIN filter_dst d ON s.k = d.k WHERE s.keep', 'Custom Scan (BloomPG Filter)'); SELECT bloompg_last_trace() LIKE '%filtered=%' AND bloompg_last_trace() LIKE '%kind=bitmap%' AS direct_filter_trace_ok; CREATE INDEX filter_dst_k_idx ON filter_dst(k); ANALYZE filter_dst; -- FILTER is a PathSet transform. These data shapes let the unmodified core -- cost model naturally select each access-path family; no enable_* switch is -- used to manufacture the plans. CREATE TABLE p1_outer_index(k integer, keep boolean); CREATE TABLE p1_outer_param(k integer, keep boolean); CREATE TABLE p1_probe(k integer, payload integer, filler text); CREATE TABLE p1_param_probe(k integer, payload integer, filler text); INSERT INTO p1_outer_index SELECT CASE WHEN g % 10 = 0 THEN g / 10 ELSE 999999 END, g % 10 = 0 FROM generate_series(1, 50000) AS g; INSERT INTO p1_outer_param SELECT g, g = 1 FROM generate_series(1, 1000) AS g; INSERT INTO p1_probe SELECT 1 + (g % 25000), 1 + (g % 25000), repeat('i', 128) FROM generate_series(1, 50000) AS g; INSERT INTO p1_param_probe SELECT 1 + (g % 200), 1 + (g % 200), repeat('p', 96) FROM generate_series(1, 200000) AS g; CREATE INDEX p1_probe_k_idx ON p1_probe(k); CREATE INDEX p1_param_probe_k_idx ON p1_param_probe(k); CLUSTER p1_probe USING p1_probe_k_idx; VACUUM (ANALYZE) p1_probe; ANALYZE p1_outer_index, p1_outer_param, p1_param_probe; SET bloompg.index_transfer = off; SET bloompg.profile = on; SET bloompg.profile_log = off; CALL assert_same('p1_plain_index', 'SELECT sum(i.payload) FROM p1_outer_index o JOIN p1_probe i ON o.k = i.k WHERE o.keep AND i.k <= 6500'); CALL assert_plan_contains('p1_plain_index_plan', 'SELECT sum(i.payload) FROM p1_outer_index o JOIN p1_probe i ON o.k = i.k WHERE o.keep AND i.k <= 6500', 'Wrapped Access Path: Index Scan'); CALL assert_plan_contains('p1_plain_index_name', 'SELECT sum(i.payload) FROM p1_outer_index o JOIN p1_probe i ON o.k = i.k WHERE o.keep AND i.k <= 6500', 'p1_probe_k_idx'); CALL assert_same('p1_index_only', 'SELECT count(*) FROM p1_outer_index o JOIN p1_probe i ON o.k = i.k WHERE o.keep AND i.k <= 6500'); CALL assert_plan_contains('p1_index_only_plan', 'SELECT count(*) FROM p1_outer_index o JOIN p1_probe i ON o.k = i.k WHERE o.keep AND i.k <= 6500', 'Wrapped Access Path: Index Only Scan'); SET bloompg.excitation_threshold = 0.002; CALL assert_same('p1_parameterized_index_rescan', 'SELECT o.k, sum(i.payload) FROM p1_outer_param o JOIN p1_param_probe i ON o.k = i.k WHERE o.keep GROUP BY o.k'); CALL assert_plan_contains('p1_parameterized_index_filter', 'SELECT sum(i.payload) FROM p1_outer_param o JOIN p1_param_probe i ON o.k = i.k WHERE o.keep', 'Custom Scan (BloomPG Filter)'); CALL assert_plan_contains('p1_parameterized_index_path', 'SELECT sum(i.payload) FROM p1_outer_param o JOIN p1_param_probe i ON o.k = i.k WHERE o.keep', 'p1_param_probe_k_idx'); SELECT COALESCE( (bloompg_last_profile()->'planning'->'path_transform'->>'parameterized')::integer > 0 AND (bloompg_last_profile()->'planning'->'path_transform'->>'input_paths')::integer = (bloompg_last_profile()->'planning'->'path_transform'->>'wrapped_paths')::integer, false) AS parameterized_pathset_preserved_ok; SET bloompg.excitation_threshold = 0.15; -- A local-qual-only Memory replacement remains semantically equivalent to -- its native parameterized paths. P1 must be allowed to choose keyed lookup -- when the outer side is tiny instead of rescanning the column store. CREATE TABLE p1_memory_outer(k integer); CREATE TABLE p1_memory_probe(k integer, keep boolean, payload integer); INSERT INTO p1_memory_outer SELECT g FROM generate_series(1, 10) AS g; INSERT INTO p1_memory_probe SELECT g, g <= 20000, g FROM generate_series(1, 200000) AS g; CREATE UNIQUE INDEX p1_memory_probe_k_idx ON p1_memory_probe(k); ANALYZE p1_memory_outer, p1_memory_probe; CALL assert_same('p1_memory_parameterized_index', 'SELECT sum(p.payload) FROM p1_memory_outer o JOIN p1_memory_probe p ON o.k = p.k WHERE p.keep'); CALL assert_plan_contains('p1_memory_parameterized_index_plan', 'SELECT sum(p.payload) FROM p1_memory_outer o JOIN p1_memory_probe p ON o.k = p.k WHERE p.keep', 'p1_memory_probe_k_idx'); SELECT COALESCE( (bloompg_last_profile()->'planning'->'path_transform' ->>'replacement_parameterized_retained')::integer > 0 AND EXISTS ( SELECT 1 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'p' AND relation->>'replacement' = 'memory'), false) AS p1_memory_parameterized_path_preserved_ok; CREATE TABLE p1_scan_source(k integer, keep boolean); CREATE TABLE p1_scan_probe(k integer, flag integer, payload text); INSERT INTO p1_scan_source SELECT CASE WHEN g % 100 = 0 THEN g / 100 ELSE 999999 END, g % 100 = 0 FROM generate_series(1, 100000) AS g; INSERT INTO p1_scan_probe SELECT 1 + (g % 2500), (g * 37) % 10000, repeat('b', 128) FROM generate_series(1, 50000) AS g; CREATE INDEX p1_scan_probe_flag_idx ON p1_scan_probe(flag); ANALYZE p1_scan_source, p1_scan_probe; SET bloompg.excitation_threshold = 0.02; CALL assert_same('p1_seq_filter', 'SELECT count(*) FROM p1_scan_source s JOIN p1_scan_probe p ON s.k = p.k WHERE s.keep'); CALL assert_plan_contains('p1_seq_filter_plan', 'SELECT count(*) FROM p1_scan_source s JOIN p1_scan_probe p ON s.k = p.k WHERE s.keep', 'Wrapped Access Path: Seq Scan'); CALL assert_same('p1_bitmap_filter', 'SELECT sum(length(p.payload)) FROM p1_scan_source s JOIN p1_scan_probe p ON s.k = p.k WHERE s.keep AND p.flag < 1000'); CALL assert_plan_contains('p1_bitmap_filter_plan', 'SELECT sum(length(p.payload)) FROM p1_scan_source s JOIN p1_scan_probe p ON s.k = p.k WHERE s.keep AND p.flag < 1000', 'Wrapped Access Path: Bitmap Heap Scan'); SET bloompg.profile = off; RESET bloompg.profile_log; RESET bloompg.index_transfer; CREATE INDEX filter_src_k_idx ON filter_src(k); ANALYZE filter_src; SET bloompg.excitation_threshold = 1.0; SET bloompg.index_transfer = on; SET bloompg.index_transfer_max_fraction = 1.0; SET bloompg.index_guard_min_rows = 0; SET bloompg.transfer_progress_metric = 'rows'; SET bloompg.profile = on; SET bloompg.profile_log = off; SELECT count(*) = 400 AS index_transfer_query_ok FROM filter_src s JOIN filter_dst d ON s.k = d.k WHERE s.keep; SELECT COALESCE(( SELECT ( (relation->'materialization'->'index_probe'->>'scans')::integer > 0 AND (relation->'materialization'->'index_probe'->>'batches')::integer > 0 AND (relation->'materialization'->'index_probe'->>'keys')::integer = 100 ) OR ( (relation->'materialization'->'access_plan'->>'exact_keys')::integer = 100 AND ( (relation->'materialization'->'access_plan'->>'index_scans')::integer > 0 OR (relation->'materialization'->'access_plan'->>'index_only_scans')::integer > 0 OR (relation->'materialization'->'access_plan'->>'bitmap_heap_scans')::integer > 0 ) ) FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'd' ), false) AS index_transfer_profile_ok; SELECT EXISTS ( SELECT 1 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'steps') AS step WHERE step->>'source' = 's' AND (step->>'selection_guard_fallback')::boolean ) AND EXISTS ( SELECT 1 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'steps') AS step WHERE step->>'source' = 'd' AND (step->>'selection_index_ready')::boolean ) AND bloompg_last_profile()->'transfer'->'summary' ->>'progress_metric' = 'rows' AS index_wait_guard_profile_ok; -- Index readiness controls the physical access path, not fixed-point -- participation. If every source is waiting, the smallest guarded relation -- must be materialized even when its current filter targets an unindexed -- column, so it can publish the later indexed-key domain. CREATE TABLE guard_seed(k integer, keep boolean); CREATE TABLE guard_middle(seed_key integer, indexed_key integer); CREATE TABLE guard_tail(indexed_key integer); INSERT INTO guard_seed SELECT g, g = 1 FROM generate_series(1, 100) AS g; INSERT INTO guard_middle SELECT g % 10, g FROM generate_series(1, 1000) AS g; INSERT INTO guard_tail SELECT g FROM generate_series(1, 1000) AS g; CREATE INDEX guard_middle_indexed_key_idx ON guard_middle(indexed_key); ANALYZE guard_seed, guard_middle, guard_tail; SET bloompg.index_transfer_max_keys = 50; CALL assert_same('index_guard_wait_break', 'SELECT count(*) FROM guard_seed s JOIN guard_middle m ON s.k = m.seed_key JOIN guard_tail t ON m.indexed_key = t.indexed_key WHERE s.keep'); SET bloompg.enable = on; SELECT count(*) = 100 AS index_guard_wait_break_query_ok FROM guard_seed s JOIN guard_middle m ON s.k = m.seed_key JOIN guard_tail t ON m.indexed_key = t.indexed_key WHERE s.keep; SELECT COALESCE( NOT (bloompg_last_profile()->'transfer'->'summary' ->>'native_fallback')::boolean AND (bloompg_last_profile()->'transfer'->'summary' ->>'applied')::boolean AND EXISTS ( SELECT 1 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'm' AND relation->>'replacement' = 'memory' AND (relation->'materialization'->'selection' ->>'fallback')::boolean AND (relation->'materialization'->>'exact_scans')::integer > 0), false) AS index_guard_wait_break_profile_ok; -- A native parameterized path must not truncate propagation in a wider graph. -- When the exact access planner rejects the key population, the scheduler -- falls back to one physical scan and continues the equality-domain transfer. CREATE TABLE protected_dimension(id integer, keep boolean); CREATE TABLE protected_source(id integer, dimension_id integer); CREATE TABLE guarded_dimension(id integer, keep boolean); CREATE TABLE protected_guarded_fact(protected_id integer, guarded_id integer); INSERT INTO protected_dimension SELECT g, g < 9 FROM generate_series(0, 9) AS g; INSERT INTO protected_source SELECT g, g % 10 FROM generate_series(1, 10000) AS g; INSERT INTO guarded_dimension SELECT g, g < 2 FROM generate_series(0, 999) AS g; INSERT INTO protected_guarded_fact SELECT 1 + (g % 10000), g % 1000 FROM generate_series(1, 100000) AS g; CREATE UNIQUE INDEX protected_source_id_idx ON protected_source(id); CREATE INDEX protected_source_dimension_idx ON protected_source(dimension_id); CREATE INDEX protected_guarded_fact_protected_idx ON protected_guarded_fact(protected_id); CREATE INDEX protected_guarded_fact_guarded_idx ON protected_guarded_fact(guarded_id); ANALYZE protected_dimension, protected_source, guarded_dimension, protected_guarded_fact; SET bloompg.index_transfer_max_fraction = 0.15; SET bloompg.index_transfer_max_keys = 50; SET bloompg.index_transfer_planner_max_keys = 1; CALL assert_same('index_guard_complete_transfer', 'SELECT count(*) FROM protected_dimension d JOIN protected_source p ON d.id = p.dimension_id JOIN protected_guarded_fact f ON p.id = f.protected_id JOIN guarded_dimension g ON g.id = f.guarded_id WHERE d.keep AND g.keep'); SET bloompg.enable = on; SELECT count(*) = 200 AS index_guard_complete_transfer_query_ok FROM protected_dimension d JOIN protected_source p ON d.id = p.dimension_id JOIN protected_guarded_fact f ON p.id = f.protected_id JOIN guarded_dimension g ON g.id = f.guarded_id WHERE d.keep AND g.keep; SELECT COALESCE( NOT (bloompg_last_profile()->'transfer'->'summary' ->>'native_fallback')::boolean AND (bloompg_last_profile()->'transfer'->'summary' ->>'applied')::boolean AND EXISTS ( SELECT 1 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'f' AND relation->>'replacement' = 'memory' AND (relation->'materialization'->>'exact_scans')::integer > 0), false) AS index_guard_complete_transfer_profile_ok; SET bloompg.profile = off; RESET bloompg.profile_log; RESET bloompg.transfer_progress_metric; RESET bloompg.index_guard_min_rows; RESET bloompg.index_transfer_max_fraction; RESET bloompg.index_transfer_max_keys; RESET bloompg.index_transfer_planner_max_keys; RESET bloompg.index_transfer; SET bloompg.excitation_threshold = 0.0; SET bloompg.profile = on; SET bloompg.profile_log = off; SELECT count(*) > 0 AS native_fallback_query_ok FROM a JOIN b ON a.k = b.k WHERE a.keep; SELECT (bloompg_last_profile()->'transfer'->'summary'->>'applied')::boolean = false AND (bloompg_last_profile()->'planning'->>'replanned')::boolean AND (bloompg_last_profile()->'planning'->>'native_preview_ms')::numeric >= 0 AS native_fallback_replanned_ok; SET bloompg.profile = off; RESET bloompg.profile_log; SET bloompg.excitation_threshold = 1.0; CREATE TABLE exact_empty_src(k integer, keep boolean); CREATE TABLE exact_empty_dst(k integer, payload text); INSERT INTO exact_empty_src SELECT g, g <= 10 FROM generate_series(1, 1000) AS g; INSERT INTO exact_empty_dst SELECT 10000 + g, repeat('e', 96) FROM generate_series(1, 2000) AS g; ANALYZE exact_empty_src, exact_empty_dst; CALL assert_same('exact_empty', 'SELECT count(*) FROM exact_empty_src s JOIN exact_empty_dst d ON s.k = d.k WHERE s.keep'); CALL assert_plan_contains('exact_empty_plan', 'SELECT count(*) FROM exact_empty_src s JOIN exact_empty_dst d ON s.k = d.k WHERE s.keep', 'Custom Scan (BloomPG Empty)'); SELECT bloompg_last_trace() LIKE '%empty=%' AS exact_empty_trace_ok; PREPARE stale_empty AS SELECT count(*) FROM exact_empty_src s JOIN exact_empty_dst d ON s.k = d.k WHERE s.keep; EXECUTE stale_empty; INSERT INTO exact_empty_dst VALUES (1, 'now visible'); EXECUTE stale_empty; DEALLOCATE stale_empty; CREATE TABLE empty_side(k integer); CREATE TABLE error_side(k integer, divisor integer); INSERT INTO error_side VALUES (1, 0), (2, 1); ANALYZE empty_side, error_side; CALL assert_same('optimizer_error_fallback', 'SELECT count(*) FROM empty_side e JOIN error_side z ON e.k = z.k WHERE 10 / z.divisor > 0'); SET bloompg.replan_after_transfer = off; CALL assert_same('direct_p0_execution', 'SELECT a.k, a.k2, b.payload FROM a JOIN b ON a.k = b.k WHERE a.keep'); SET bloompg.replan_after_transfer = on; SET bloompg.materialization_memory = '64kB'; SET bloompg.late_materialize = off; CALL assert_same('memory_budget_fallback', 'SELECT count(*), sum(length(a.payload)) FROM a JOIN b ON a.k = b.k WHERE a.k < 200'); SELECT bloompg_last_trace() LIKE '%fallback=optimizer_execution_error sqlstate=54000%' AS memory_budget_fallback_ok; CALL assert_same('memory_budget_fallback_repeat', 'SELECT count(*), sum(length(a.payload)) FROM a JOIN b ON a.k = b.k WHERE a.k < 200'); RESET bloompg.materialization_memory; SET bloompg.late_materialize = on; SET bloompg.enable = on; SELECT count(*) > 0 AND sum(length(a.payload)) > 0 AS wide_result_ok FROM a JOIN b ON a.k = b.k WHERE a.keep; SELECT bloompg_last_trace() LIKE '%transfers=%' AS transfer_trace_ok; SET max_parallel_workers_per_gather = 4; SET min_parallel_table_scan_size = 0; SET min_parallel_index_scan_size = 0; SET parallel_setup_cost = 0; SET parallel_tuple_cost = 0; CALL assert_same('parallel_wide', 'SELECT a.k, a.k2, b.payload FROM a JOIN b ON a.k = b.k WHERE a.keep'); CALL assert_same('parallel_composite', 'SELECT a.k, count(*) FROM a JOIN b ON a.k = b.k AND a.k2 = b.k2 WHERE a.keep GROUP BY a.k'); CALL assert_same('parallel_partition', 'SELECT count(*) FROM pa JOIN b ON pa.k = b.k WHERE pa.keep'); CALL assert_plan_contains('parallel_plan', 'SELECT count(*) FROM a JOIN b ON a.k = b.k WHERE a.keep', 'Parallel Custom Scan (BloomPG'); -- A partial join can use a regular replacement as its replicated inner side, -- while a higher nested loop repeatedly launches an inner Gather. Both the -- worker-local cursor and the Gather-wide shared cursor must restart at their -- own lifecycle boundary. CREATE TABLE parallel_rescan_gate(kind integer, keep boolean); CREATE TABLE parallel_rescan_hub(k integer, kind integer, keep boolean); CREATE TABLE parallel_rescan_fact(k integer, payload integer); INSERT INTO parallel_rescan_gate SELECT g, g = 1 FROM generate_series(1, 200) AS g; INSERT INTO parallel_rescan_hub SELECT g, 1 + (g % 100), g <= 200 FROM generate_series(1, 1000) AS g; INSERT INTO parallel_rescan_fact SELECT 1 + (g % 1000), g FROM generate_series(1, 5000) AS g; ANALYZE parallel_rescan_gate, parallel_rescan_hub, parallel_rescan_fact; SET enable_hashjoin = off; SET enable_mergejoin = off; SET enable_material = off; CALL assert_same('parallel_nested_loop_rescan', 'SELECT g.kind, h.k, f.payload FROM parallel_rescan_gate g JOIN parallel_rescan_hub h ON g.kind = h.kind JOIN parallel_rescan_fact f ON h.k = f.k WHERE g.keep AND h.keep'); CALL assert_parallel_rescan_plan('parallel_nested_loop_rescan_plan', 'SELECT g.kind, h.k, f.payload FROM parallel_rescan_gate g JOIN parallel_rescan_hub h ON g.kind = h.kind JOIN parallel_rescan_fact f ON h.k = f.k WHERE g.keep AND h.keep'); RESET enable_hashjoin; RESET enable_mergejoin; RESET enable_material; CREATE TABLE parallel_filter_src(k integer, keep boolean); CREATE TABLE parallel_filter_dst(k integer, payload text); INSERT INTO parallel_filter_src SELECT g, g <= 5 FROM generate_series(1, 1000) AS g; INSERT INTO parallel_filter_dst SELECT 1 + (g % 200), repeat('p', 96) FROM generate_series(1, 20000) AS g; ANALYZE parallel_filter_src, parallel_filter_dst; SET bloompg.excitation_threshold = 0.01; SET parallel_leader_participation = off; CALL assert_same('parallel_direct_filter', 'SELECT count(*) FROM parallel_filter_src s JOIN parallel_filter_dst d ON s.k = d.k WHERE s.keep'); CALL assert_plan_contains('parallel_direct_filter_plan', 'SELECT count(*) FROM parallel_filter_src s JOIN parallel_filter_dst d ON s.k = d.k WHERE s.keep', 'Parallel Filter DSM: true'); RESET parallel_leader_participation; SET bloompg.excitation_threshold = 1.0; CREATE TABLE parallel_wave_hub(k integer, keep boolean); CREATE TABLE parallel_wave_left(k integer, payload integer); CREATE TABLE parallel_wave_right(k integer, payload integer); INSERT INTO parallel_wave_hub SELECT g, g <= 5 FROM generate_series(1, 1000) AS g; INSERT INTO parallel_wave_left SELECT 1 + (g % 200), g FROM generate_series(1, 20000) AS g; INSERT INTO parallel_wave_right SELECT 1 + (g % 200), g FROM generate_series(1, 20000) AS g; ANALYZE parallel_wave_hub, parallel_wave_left, parallel_wave_right; SET bloompg.transfer_parallel_min_rows = 0; SET bloompg.transfer_workers = 4; SET bloompg.profile = on; SET bloompg.profile_log = off; CALL assert_same('parallel_transfer_wave', 'SELECT count(*) FROM parallel_wave_hub h JOIN parallel_wave_left l USING (k) JOIN parallel_wave_right r USING (k) WHERE h.keep'); SET bloompg.enable = on; SELECT count(*) = 50000 AS parallel_wave_query_ok FROM parallel_wave_hub h JOIN parallel_wave_left l USING (k) JOIN parallel_wave_right r USING (k) WHERE h.keep; SELECT (bloompg_last_profile()->'transfer'->'summary'->>'parallel_append_waves')::integer >= 1 AND (bloompg_last_profile()->'transfer'->'summary'->>'parallel_waves')::integer < (bloompg_last_profile()->'transfer'->'summary'->>'parallel_scans')::integer AND (bloompg_last_profile()->'transfer'->'summary' ->>'parallel_workers_launched')::integer > 0 AND ( SELECT count(*) FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->'materialization'->'wave'->>'kind' = 'parallel-append' AND relation->'materialization'->'wave'->>'layout' = 'compact' ) = 2 AS parallel_wave_profile_ok; -- A SubLink can restrict the whole query without making unrelated detached -- relation scans unsafe. Those local scans must retain parallel transfer. CALL assert_same('parallel_transfer_restricted_query', 'SELECT count(*) FROM parallel_wave_hub h JOIN parallel_wave_left l USING (k) JOIN parallel_wave_right r USING (k) WHERE h.keep AND h.k <= (SELECT max(h2.k) FROM parallel_wave_hub h2 WHERE h2.k = h.k)'); SET bloompg.enable = on; SELECT count(*) = 50000 AS parallel_restricted_query_ok FROM parallel_wave_hub h JOIN parallel_wave_left l USING (k) JOIN parallel_wave_right r USING (k) WHERE h.keep AND h.k <= (SELECT max(h2.k) FROM parallel_wave_hub h2 WHERE h2.k = h.k); SELECT (bloompg_last_profile()->'transfer'->'summary' ->>'parallel_workers_launched')::integer > 0 AS parallel_restricted_source_scan_ok; -- Exercise the worker packet path itself, including NULL and toasted varlena -- values. The leader should import vectors without reconstructing slots. CREATE TABLE parallel_pack_hub(k integer, keep boolean); CREATE TABLE parallel_pack_source(k integer, payload text); INSERT INTO parallel_pack_hub SELECT g, g <= 5 FROM generate_series(1, 1000) AS g; INSERT INTO parallel_pack_source SELECT 1 + (g % 200), CASE WHEN g % 113 = 0 THEN NULL WHEN g % 1000 = 0 THEN repeat('x', 5000) ELSE repeat('x', 1 + (g % 7)) END FROM generate_series(1, 320000) AS g; ANALYZE parallel_pack_hub, parallel_pack_source; CALL assert_same('parallel_transfer_vector_packet', 'SELECT sum(length(s.payload)) > 0 FROM parallel_pack_hub h JOIN parallel_pack_source s USING (k) WHERE h.keep'); SET bloompg.enable = on; SELECT sum(length(s.payload)) > 0 AS parallel_packet_query_ok FROM parallel_pack_hub h JOIN parallel_pack_source s USING (k) WHERE h.keep; SELECT COALESCE(( SELECT relation->'materialization'->>'access_path' = 'parallel-p0' AND (relation->'materialization'->'parallel' ->>'workers_launched')::integer > 0 AND (relation->'materialization'->'store' ->>'varlen_values')::integer > 0 AND (relation->'materialization'->'store' ->>'slot_reconstructions')::integer = 0 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 's' ), false) AS parallel_packet_profile_ok; CREATE TABLE heterogeneous_wave_hub(k integer, keep boolean); CREATE TABLE heterogeneous_wave_left(k integer, payload text); CREATE TABLE heterogeneous_wave_right(k integer, payload bigint); INSERT INTO heterogeneous_wave_hub SELECT g, g <= 5 FROM generate_series(1, 1000) AS g; INSERT INTO heterogeneous_wave_left SELECT 1 + (g % 200), CASE WHEN g % 113 = 0 THEN NULL WHEN g % 10 = 0 THEN repeat('x', 5000) ELSE repeat('x', 1 + (g % 7)) END FROM generate_series(1, 20000) AS g; INSERT INTO heterogeneous_wave_right SELECT 1 + (g % 200), g FROM generate_series(1, 20000) AS g; ANALYZE heterogeneous_wave_hub, heterogeneous_wave_left, heterogeneous_wave_right; CALL assert_same('heterogeneous_parallel_transfer_wave', 'SELECT sum(length(l.payload)::bigint + r.payload) > 0 FROM heterogeneous_wave_hub h JOIN heterogeneous_wave_left l USING (k) JOIN heterogeneous_wave_right r USING (k) WHERE h.keep'); SET bloompg.enable = on; SELECT sum(length(l.payload)::bigint + r.payload) > 0 AS heterogeneous_wave_query_ok FROM heterogeneous_wave_hub h JOIN heterogeneous_wave_left l USING (k) JOIN heterogeneous_wave_right r USING (k) WHERE h.keep; SELECT ( SELECT count(*) FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->'materialization'->'wave'->>'kind' = 'parallel-append' AND relation->'materialization'->'wave'->>'layout' = 'heterogeneous' ) = 2 AS heterogeneous_wave_profile_ok; RESET bloompg.transfer_parallel_min_rows; RESET bloompg.transfer_workers; SET bloompg.profile = off; RESET bloompg.profile_log; SELECT count(*) = 0 AS filter_contexts_released FROM pg_backend_memory_contexts WHERE name LIKE 'BloomPG%filter%'; SET max_parallel_workers_per_gather = 0; SET bloompg.profile = on; SET bloompg.profile_log = off; SET bloompg.late_materialize = off; SELECT count(*) > 0 AS correlated_projection_query_ok FROM a JOIN c ON a.k = c.k WHERE a.keep AND EXISTS ( SELECT 1 FROM b WHERE b.k = a.k AND b.k2 = a.k2); SELECT COALESCE(( SELECT (relation->'projection'->>'columns')::integer = 3 AND ARRAY( SELECT (attribute->>'attno')::integer FROM jsonb_array_elements( relation->'projection'->'attributes') AS attribute ORDER BY (attribute->>'attno')::integer ) = ARRAY[1, 2, 5] FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'a' ), false) AS correlated_projection_profile_ok; -- Track an outer Var through more than one SubLink level. The expected set -- is semantic (join key, correlated key, local qual), not a physical-width -- shortcut tied to a benchmark table. SELECT count(*) > 0 AS nested_correlated_projection_query_ok FROM a JOIN c ON a.k = c.k WHERE a.keep AND EXISTS ( SELECT 1 FROM b WHERE b.k = a.k AND EXISTS ( SELECT 1 FROM c AS nested_c WHERE nested_c.k = b.k AND nested_c.k2 = a.k2)); SELECT COALESCE(( SELECT ARRAY( SELECT (attribute->>'attno')::integer FROM jsonb_array_elements( relation->'projection'->'attributes') AS attribute ORDER BY (attribute->>'attno')::integer ) = ARRAY[1, 2, 5] FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'a' ), false) AS nested_correlated_projection_profile_ok; -- Whole-row and system-column Vars deliberately keep the native scan tuple -- shape. Projection is an optimization and must fail closed for both. SELECT count(a) > 0 AS whole_row_projection_query_ok FROM a JOIN c ON a.k = c.k WHERE a.keep; SELECT COALESCE(( SELECT (relation->'projection'->>'columns')::integer = (relation->'projection'->>'original_columns')::integer FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'a' ), false) AS whole_row_projection_fail_closed_ok; SELECT count(a.ctid) > 0 AS system_column_projection_query_ok FROM a JOIN c ON a.k = c.k WHERE a.keep; SELECT COALESCE(( SELECT (relation->'projection'->>'columns')::integer = (relation->'projection'->>'original_columns')::integer FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'a' ), false) AS system_column_projection_fail_closed_ok; SET bloompg.late_materialize = on; SELECT count(*) > 0 AS profiled_query_ok FROM a JOIN c ON a.k = c.k WHERE a.keep; SELECT bloompg_last_profile()->>'status' = 'complete' AND (bloompg_last_profile()->'planning'->>'transfer_ms')::numeric >= 0 AND (bloompg_last_profile()->'planning'->>'statistics_ms')::numeric >= 0 AND jsonb_typeof(bloompg_last_profile()->'transfer'->'steps') = 'array' AND jsonb_array_length(bloompg_last_profile()->'transfer'->'relations') >= 2 AND (bloompg_last_profile()->'transfer'->'summary'->>'statistics_rows')::numeric >= 0 AND (bloompg_last_profile()->'transfer'->'summary'->>'statistics_key_sets')::integer > 0 AND (bloompg_last_profile()->'transfer'->'summary') ? 'precomputed_key_sets' AND (bloompg_last_profile()->'transfer'->'summary') ? 'scanned_key_sets' AND (bloompg_last_profile()->'transfer'->'summary') ? 'one_pass_bitmap_builds' AND (bloompg_last_profile()->'transfer'->'summary') ? 'filter_finalize_rescans' AND (bloompg_last_profile()->'transfer'->'summary'->>'parallel_waves')::integer <= (bloompg_last_profile()->'transfer'->'summary'->>'parallel_scans')::integer AND (bloompg_last_profile()->'transfer'->'relations'->0->'sampling') ? 'probe_filter_checks' AND (bloompg_last_profile()->'transfer'->'relations'->0) ? 'intermediate_sampling' AND (bloompg_last_profile()->'transfer'->'relations'->0->'materialization') ? 'one_pass_bitmap_builds' AND (bloompg_last_profile()->'transfer'->'relations'->0->'materialization') ? 'filter_finalize_rescans' AND bloompg_last_profile()->'executor' ? 'run_ms' AS structured_profile_ok; CREATE TABLE intermediate_parent(k integer, keep boolean); CREATE TABLE intermediate_child(k integer); INSERT INTO intermediate_parent SELECT g, g <= 10 FROM generate_series(1, 100) AS g; INSERT INTO intermediate_child SELECT 1 + g % 100 FROM generate_series(1, 1000) AS g; ANALYZE intermediate_parent, intermediate_child; SET bloompg.transfer_progress_metric = 'rows'; SELECT count(*) = 100 AS intermediate_sample_query_ok FROM intermediate_parent AS p JOIN intermediate_child AS c USING (k) WHERE p.keep; SELECT COALESCE(( SELECT (relation->'intermediate_sampling'->>'calls')::integer > 0 AND (relation->'intermediate_sampling'->>'last_fraction')::numeric = 1.0 FROM jsonb_array_elements( bloompg_last_profile()->'transfer'->'relations') AS relation WHERE relation->>'alias' = 'p' ), false) AS intermediate_same_domain_ok; RESET bloompg.transfer_progress_metric; SET bloompg.profile = off; RESET bloompg.profile_log; DROP SCHEMA bloompg_regress CASCADE; DROP EXTENSION bloompg; RESET client_min_messages;