From 0c7be87a7ddd2f25c9961fe12319bd01e3b67b71 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Fri, 16 Oct 2026 01:36:07 +0000 Subject: [PATCH] test: the runtime filter's fact-qual arms need a qual that removes matching rows (#1339) held for all 150 matching rows, and the shell twin's conjunction (extra > 40) for all 200, so an answer that ignored the qual equalled heap's. Measured with the columnar scan's qual cleared before ExecScan: main's arms in both twins stay green. The qual now names the same non-key column (the original bug's trigger) and removes a third or a quarter of the matches: n % 3 <> 0, and extra % 4 <> 0. A premise in each twin checks on heap that it removes rows (100 of 150, 150 of 200). Under the same mutant both arms are now red: got 200 where heap has 150, and 150 where heap has 100. Green on PG15 to 19 (56/56 and 59 checks). Co-Authored-By: Claude Opus 5.5 --- test/check_ledger.tsv | 1 + test/check_ledger_budget.txt | 13 ++++++++++++- test/native_join_runtime_filter.sh | 8 ++++++-- test/pytest/test_join_runtime_filter.py | 13 ++++++++++--- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/test/check_ledger.tsv b/test/check_ledger.tsv index 897d0f8ae..17fa55ab4 100644 --- a/test/check_ledger.tsv +++ b/test/check_ledger.tsv @@ -2614,6 +2614,7 @@ native_join_runtime_filter native_join_runtime_filter premise: heap's answer und native_join_runtime_filter native_join_runtime_filter premise: heap's answer under a true gate over a heap dimension holds 200 rows 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter premise: heap's empty-then-full rescan answer holds the 200 keys 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter premise: the empty-then-full rescan runs under the coordinator 15;16;17;18;19 never - +native_join_runtime_filter native_join_runtime_filter premise: the fact qual removes matching rows on heap 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter premise: the large-dimension empty build runs under the coordinator 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter premise: with no gate, the columnar-dimension join has the coordinator 15;16;17;18;19 never - native_join_runtime_filter native_join_runtime_filter premise: with no gate, the heap-dimension join has the coordinator 15;16;17;18;19 never - diff --git a/test/check_ledger_budget.txt b/test/check_ledger_budget.txt index 20167b844..62ff83bc3 100644 --- a/test/check_ledger_budget.txt +++ b/test/check_ledger_budget.txt @@ -3206,4 +3206,15 @@ suites_not_covered 203 # never 2883 + 12 = 2895 # dated 1108 + 3 = 1111 # 2895 + 1111 == 4006 -checks_never_observed_red 2895 +# +# #1339, native_join_runtime_filter's fact-qual conjunction removed no matching row +# (jdatcmd): 2895 -> 2896. Its qual held for all 200 matches, so an answer that +# ignored it equalled heap's; the qual now removes 50 of them, and one premise checks +# that on heap. Five-major runs, 56/56 on each of 15-19. The conjunction arm keeps its +# date; the premise arrives never. +# +# ledger rows 4006 + 1 = 4007 +# never 2895 + 1 = 2896 +# dated 1111 + 0 = 1111 +# 2896 + 1111 == 4007 +checks_never_observed_red 2896 diff --git a/test/native_join_runtime_filter.sh b/test/native_join_runtime_filter.sh index ca1fde0a1..682ac3488 100755 --- a/test/native_join_runtime_filter.sh +++ b/test/native_join_runtime_filter.sh @@ -339,10 +339,14 @@ ANALYZE dim_fq; ANALYZE fact_fq; SQL )" >/dev/null -SQLFQ="SELECT count(*),sum(fact_fq.k),sum(fact_fq.extra) FROM fact_fq JOIN dim_fq ON fact_fq.k=dim_fq.k WHERE fact_fq.k > 100 AND fact_fq.extra > 40" +SQLFQ="SELECT count(*),sum(fact_fq.k),sum(fact_fq.extra) FROM fact_fq JOIN dim_fq ON fact_fq.k=dim_fq.k WHERE fact_fq.k > 100 AND fact_fq.extra % 4 <> 0" onfq="$(pc "${rf_on}SET max_parallel_workers_per_gather=0;SET enable_nestloop=off;SET enable_mergejoin=off;EXPLAIN(ANALYZE,TIMING off,SUMMARY off)$SQLFQ")" check "fact-qual plan has coordinator" "$(grep -c 'Columnar Runtime Filter Coordinator' <<<"$onfq")" 1 -check "fact-qual conjunction equals heap" "$(pc "${rf_on}$SQLFQ"|tail -1)" "$(q "SELECT count(*),sum(heap_fq.k),sum(heap_fq.extra) FROM heap_fq JOIN dim_fq ON heap_fq.k=dim_fq.k WHERE heap_fq.k > 100 AND heap_fq.extra > 40")" +# The conjunction must REMOVE some matching rows (#1339). "extra > 40" held for +# every one of the 200 matches, so an answer that ignored the qual equalled heap's. +check "premise: the fact qual removes matching rows on heap" \ + "$(q "SELECT count(*) FROM heap_fq JOIN dim_fq ON heap_fq.k=dim_fq.k WHERE heap_fq.k > 100 AND heap_fq.extra % 4 <> 0")|$(q "SELECT count(*) FROM heap_fq JOIN dim_fq ON heap_fq.k=dim_fq.k")" "150|200" +check "fact-qual conjunction equals heap" "$(pc "${rf_on}$SQLFQ"|tail -1)" "$(q "SELECT count(*),sum(heap_fq.k),sum(heap_fq.extra) FROM heap_fq JOIN dim_fq ON heap_fq.k=dim_fq.k WHERE heap_fq.k > 100 AND heap_fq.extra % 4 <> 0")" # A gating qual, a WHERE that names no column (#1437). The planner puts a # Result with a One-Time Filter over the join, so the coordinator's child was a # Result rather than the Hash Join it expects, and every such query failed with diff --git a/test/pytest/test_join_runtime_filter.py b/test/pytest/test_join_runtime_filter.py index e76f30b35..c4a462f60 100644 --- a/test/pytest/test_join_runtime_filter.py +++ b/test/pytest/test_join_runtime_filter.py @@ -696,7 +696,7 @@ def test_fact_qual_with_late_mat_off_matches_heap(pgc_conn, expect): sql = """ SELECT count(*), sum(factq.k), sum(factq.n) FROM factq JOIN dimq ON factq.k = dimq.k - WHERE factq.n > 80 + WHERE factq.n % 3 <> 0 """ cur.execute( "EXPLAIN (ANALYZE, FORMAT JSON, COSTS OFF, TIMING OFF, SUMMARY OFF) " + sql @@ -708,11 +708,18 @@ def test_fact_qual_with_late_mat_off_matches_heap(pgc_conn, expect): """ SELECT count(*), sum(heapq.k), sum(heapq.n) FROM heapq JOIN dimq ON heapq.k = dimq.k - WHERE heapq.n > 80 + WHERE heapq.n % 3 <> 0 """ ) heap = cur.fetchone() - + # The qual must REMOVE some matching rows (#1339). "n > 80" held for every + # one of the 150 matches, so an answer that ignored the qual equalled heap's. + cur.execute("SELECT count(*) FROM heapq JOIN dimq ON heapq.k = dimq.k WHERE heapq.n % 3 <> 0") + kept = cur.fetchone()[0] + cur.execute("SELECT count(*) FROM heapq JOIN dimq ON heapq.k = dimq.k") + matched = cur.fetchone()[0] + + expect.text(f"{kept}|{matched}", "100|150", "premise: the fact qual removes matching rows on heap") expect.plan_node( plan, provider="Columnar Runtime Filter Coordinator",