This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-24976-9f21155c48faac3f1658853ad6a55bd314a322ed
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit 790b110df7e02ccfdcf152a49e18d87622aa982d
Author: Adrian Garcia Badaracco <[email protected]>
AuthorDate: Wed Sep 9 12:28:00 2026 +0000

    bench(predicate_eval): add expensive-first and wide-column shapes (#25032)
    
    ## Which issue does this PR close?
    
    - Part of #22883
    - Related to #11262
    - Prequel split out of #22698: these are the benchmark-suite additions
    from that PR, carried on their own so they can land (and be run)
    independently of the adaptive-filter work.
    
    ## Rationale for this change
    
    The `predicate_eval` suite (the conjunctive-filter microbenchmarks added
    for adaptive predicate ordering in #11262) was missing a few shapes that
    distinguish evaluation strategies for AND chains, and it had no result
    checking at all:
    
    1. **An expensive-selective conjunct written *before* a
    cheap-unselective one.** The suite already had the reverse (`costsel`
    q03: cheap `c0 < 90` at ~90% first, expensive `regexp_like(s, 'rare')`
    at ~0.1% second). Without the mirror shape there is no case in the suite
    where the as-written order is already the optimal one, so the suite
    cannot tell "reordered well" apart from "reordered at all". It also
    exercises a distinct code path: measuring a *cheap* conjunct on the
    small survivor batch left after pre-selection by an expensive one,
    rather than measuring an expensive conjunct on a full batch.
    
    2. **A many-column table.** Every existing `cardinality` query runs over
    the 16-column `ints` dataset, so the cost of materializing a filtered
    batch per level is small and roughly constant across the suite.
    Re-running the k = 8 predicate over a 64-column table separates the cost
    of *evaluating* the conjuncts from the cost of *filtering the batch* at
    each level, which is what per-level batch filtering actually pays for on
    wide inputs.
    
    3. **No result validation.** The suite's template had no `result`
    directive, so `--result-mode validate` verified nothing on it. A
    reordering under test must not change *which* rows survive the filter,
    and every query here is a `count(*)` whose value is fixed by the
    generated data — a checked-in expected count is therefore a free
    correctness check on top of the timing.
    
    4. **Nullable conjuncts.** Every predicate in the suite was
    non-nullable, so the suite never exercised the one case where writing
    the selective conjunct first buys nothing: `BinaryExpr`'s AND
    pre-selection bails out when the left-hand boolean array contains any
    NULL.
    
    5. **Drift that a warm-up cannot absorb.** The existing `drift` dataset
    flipped which conjunct is selective late enough that a one-shot warm-up
    already lands on the post-flip order and is right for most of the scan.
    There was no shape where a decide-once strategy is wrong for a large
    part of the scan, and none at all where the *right* order differs
    between concurrently running partitions rather than over time.
    
    ## What changes are included in this PR?
    
    Everything is under `benchmarks/` (plus the suite's own docs); there are
    no Rust changes, and the suite discovers new `.benchmark` files
    automatically.
    
    - **Result validation.** The shared template gains `result
    sql_benchmarks/predicate_eval/results/${NAME}.csv`, and every
    benchmark's expected `count(*)` is checked in under
    `predicate_eval/results/`, so `--result-mode validate` now checks that a
    reordering under test still returns the same rows. The counts were
    persisted at the suite defaults (`PRED_ROWS=1000000`, `PRED_FILL=30`),
    so validation assumes those; the `scale` and `width` subgroups pin their
    own values as template parameters (which win over the environment) and
    validate at any setting. The template's old `count(*) > 0` assert is
    dropped as redundant with this.
    - **`costsel` q04** — the mirror of q03: expensive-selective
    `regexp_like(s, 'rare')` written before cheap-unselective `c0 < 90`, so
    the as-written order is already the best one. q03/q04 bracket a
    reorderer when cost and selectivity point the same way.
    - **`cardinality` q34 and `load/ints_wide.sql`** — q32's k = 8 predicate
    over a 64-column table. `c0..c15` reuse `ints.sql`'s multipliers (all
    coprime to 100), so the hidden selectivities are identical to q32's and
    only the batch width changes.
    - **`nulls` subgroup (q90, q91).** `ints.sql` gains `c_sel`, NULL on 10%
    of rows and uniform on `[0,100)` elsewhere, so `c_sel < 5` is true on
    exactly 4% of rows. `check_short_circuit` returns
    `ShortCircuitStrategy::None` as soon as the left-hand boolean array has
    `null_count() > 0`, so the nullable selective conjunct gates nothing
    wherever it is written — q90 writes it first, q91 last. No `ints` query
    references `c_sel`, so their counts are unchanged.
    - **`drift` down to three cases.** q80 keeps the in-memory dataset, with
    the flip moved to 2% of the rows so it lands inside the pooled 8-batch
    warm-up (~65k rows at the default size). The mirrored-order twin was
    dropped as the same measurement. q82 and q83 share
    `load/drift_split.sql`: 16 Parquet files of `PRED_ROWS / 16` rows in a
    gitignored `predicate_eval/scratch/`, `f00..f07` with `a_sel = 0`
    selective (~0.1%) against `b_sel = 0` at ~50% and `f08..f15` the mirror.
    `FileGroup::split_files` sorts a group's files by path, so
    `target_partitions = 1` (q82) reads them in order and meets the flip
    halfway through one stream, and `= 16` (q83) gives every stream one
    whole file and one fixed profile — the case a decide-once or
    shared-decision strategy cannot get right. `repartition_file_scans =
    false` lives in the shared load file (it stops the groups being
    re-derived as byte ranges); `target_partitions` differs per query, so
    each `.benchmark` sets its own in an `init` step. The file names are
    fixed rather than written by one `COPY ... PARTITIONED BY`, because a
    directory-target `COPY` names its output with a random write id and
    Parquet has no overwrite, so the second load of the shared dataset would
    add files instead of replacing them.
    - **Shared queries for the knob sweeps.** The template takes a `QUERY`
    parameter (the path stem under `queries/`), so the `scale` and `width`
    stubs point at `costsel/q03` and `costsel/q01` rather than keeping seven
    verbatim copies of two WHERE clauses.
    - **Docs.** The `predicate_eval` row in
    `benchmarks/sql_benchmarks/README.md` carries the subgroup list, the
    result-validation behaviour with its defaults caveat, and the note that
    the suite sets no engine config of its own; `bench.sh` and the `.suite`
    description match.
    
    ## What is the testing strategy for this PR?
    
    No unit tests: these are benchmark definitions, not library code.
    Verification is that the suite parses, every query runs, and every
    query's result matches its checked-in expected count.
    
    A full run passes on a release build: `benchmark_runner predicate_eval
    -i 1 --result-mode validate` runs all 31 benchmarks across the ten
    subgroups and exits 0, and repeats cleanly (the drift `COPY`s overwrite
    their files rather than accumulating).
    
    Validation was checked to actually be checking: overwriting one expected
    count (`nulls_q90` 20000 → 999999) fails the run with
    
    ```
    Error: Execution error: Error in result on row 1, column 1 running query 
"": expected value "999999" but got value "20000" in row: ["20000"]
    ```
    
    and restoring the file makes it pass again; the same is true of
    `drift_q83_per_partition_skew` at 991 instead of 992.
    
    The drift plans are as intended on both sides (`EXPLAIN`, release build,
    file list capped at five entries):
    
    ```
    -- q82, target_partitions = 1
    FilterExec: a_sel@0 = 0 AND b_sel@1 = 0, projection=[]
      DataSourceExec: file_groups={1 group: [[.../drift_split/f00.parquet, 
.../f01.parquet, .../f02.parquet, .../f03.parquet, .../f04.parquet, ...]]}, 
projection=[a_sel, b_sel], file_type=parquet, predicate=a_sel@1 = 0 AND b_sel@2 
= 0, ...
    
    -- q83, target_partitions = 16
    FilterExec: a_sel@0 = 0 AND b_sel@1 = 0, projection=[]
      DataSourceExec: file_groups={16 groups: [[.../drift_split/f00.parquet], 
[.../f01.parquet], [.../f02.parquet], [.../f03.parquet], [.../f04.parquet], 
...]}, projection=[a_sel, b_sel], file_type=parquet, predicate=a_sel@1 = 0 AND 
b_sel@2 = 0, ...
    ```
    
    Both settings were checked to be load-bearing by dropping them: without
    q82's `init` the plan no longer contains `file_groups={1 group`, and
    with `repartition_file_scans` left on, q83 plans as `file_groups={12
    groups: [[.../f00.parquet:0..224901, .../f01.parquet:0..74967], ...]}` —
    both profiles in one stream. That the read order is deterministic is
    what q82 relies on, and it is: `FileGroup::split_files` sorts a group's
    files by path before chunking them
    
([`file_groups.rs`](https://github.com/apache/datafusion/blob/main/datafusion/datasource/src/file_groups.rs#L459-L462),
    whose comment says why: `ObjectStore::list` gives no order), and
    `FileStream` scans a group's files in order.
    
    The datasets were verified directly too: `c_sel` is NULL on exactly
    100000 rows and `< 5` on 40000 at `PRED_ROWS=1000000`; the split dataset
    has 62500 rows per file, `a_sel = 0` on 62 of them and `b_sel = 0` on
    31250 in `f00..f07`, mirrored in `f08..f15`, and 992 rows matching both
    over the whole table. q34 and q32 have identical selectivities (both
    `count(*) = 5000` at `PRED_ROWS=100000`).
    
    ## Are there any user-facing changes?
    
    No. Benchmark definitions only; nothing in this PR is part of any public
    API or affects query execution.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    ---------
    
    Co-authored-by: Claude Fable 5.1 <[email protected]>
---
 benchmarks/bench.sh                                | 10 ++-
 benchmarks/sql_benchmarks/README.md                |  2 +-
 .../benchmarks/cardinality/q30.benchmark           |  3 +-
 .../benchmarks/cardinality/q31.benchmark           |  3 +-
 .../benchmarks/cardinality/q32.benchmark           |  3 +-
 .../benchmarks/cardinality/q33.benchmark           |  3 +-
 .../cardinality/{q32.benchmark => q34.benchmark}   |  7 +-
 .../benchmarks/correlation/q70.benchmark           |  3 +-
 .../benchmarks/correlation/q71.benchmark           |  3 +-
 .../benchmarks/correlation/q72.benchmark           |  3 +-
 .../benchmarks/correlation/q73.benchmark           |  3 +-
 .../predicate_eval/benchmarks/cost/q10.benchmark   |  3 +-
 .../predicate_eval/benchmarks/cost/q11.benchmark   |  3 +-
 .../benchmarks/costsel/q01.benchmark               |  3 +-
 .../benchmarks/costsel/q02.benchmark               |  3 +-
 .../benchmarks/costsel/q03.benchmark               |  3 +-
 .../costsel/{q03.benchmark => q04.benchmark}       |  5 +-
 .../predicate_eval/benchmarks/drift/q80.benchmark  |  3 +-
 .../predicate_eval/benchmarks/drift/q81.benchmark  |  7 --
 .../predicate_eval/benchmarks/drift/q82.benchmark  | 10 +++
 .../predicate_eval/benchmarks/drift/q83.benchmark  | 10 +++
 .../benchmarks/neutral/q60.benchmark               |  3 +-
 .../benchmarks/neutral/q61.benchmark               |  3 +-
 .../{neutral/q60.benchmark => nulls/q90.benchmark} |  7 +-
 .../{neutral/q60.benchmark => nulls/q91.benchmark} |  7 +-
 .../predicate_eval/benchmarks/scale/q50.benchmark  |  3 +-
 .../predicate_eval/benchmarks/scale/q51.benchmark  |  3 +-
 .../predicate_eval/benchmarks/scale/q52.benchmark  |  3 +-
 .../predicate_eval/benchmarks/scale/q53.benchmark  |  3 +-
 .../benchmarks/selectivity/q20.benchmark           |  3 +-
 .../benchmarks/selectivity/q21.benchmark           |  3 +-
 .../predicate_eval/benchmarks/width/q40.benchmark  |  3 +-
 .../predicate_eval/benchmarks/width/q41.benchmark  |  3 +-
 .../predicate_eval/benchmarks/width/q42.benchmark  |  3 +-
 .../sql_benchmarks/predicate_eval/load/corr.sql    | 15 +---
 .../predicate_eval/load/corrproxy.sql              | 34 +++-----
 .../sql_benchmarks/predicate_eval/load/drift.sql   | 19 ++---
 .../predicate_eval/load/drift_split.sql            | 96 ++++++++++++++++++++++
 .../sql_benchmarks/predicate_eval/load/ints.sql    | 13 +--
 .../predicate_eval/load/ints_wide.sql              | 71 ++++++++++++++++
 .../sql_benchmarks/predicate_eval/load/markers.sql | 17 ++--
 .../sql_benchmarks/predicate_eval/load/mixed.sql   | 16 +---
 .../predicate_eval.benchmark.template              | 37 +++------
 .../predicate_eval/predicate_eval.suite            |  2 +-
 .../predicate_eval/queries/cardinality/q30.sql     |  4 +-
 .../predicate_eval/queries/cardinality/q31.sql     |  2 +-
 .../predicate_eval/queries/cardinality/q32.sql     |  2 +-
 .../predicate_eval/queries/cardinality/q33.sql     |  2 +-
 .../queries/cardinality/{q32.sql => q34.sql}       |  2 +-
 .../predicate_eval/queries/correlation/q70.sql     |  4 +-
 .../predicate_eval/queries/correlation/q71.sql     |  4 +-
 .../predicate_eval/queries/correlation/q72.sql     |  4 +-
 .../predicate_eval/queries/correlation/q73.sql     | 10 +--
 .../predicate_eval/queries/cost/q10.sql            |  4 +-
 .../predicate_eval/queries/cost/q11.sql            |  3 +-
 .../predicate_eval/queries/costsel/q01.sql         |  6 +-
 .../predicate_eval/queries/costsel/q02.sql         |  3 +-
 .../predicate_eval/queries/costsel/q03.sql         |  4 +-
 .../predicate_eval/queries/costsel/q04.sql         |  4 +
 .../predicate_eval/queries/drift/q80.sql           |  6 +-
 .../predicate_eval/queries/drift/q81.sql           |  5 --
 .../predicate_eval/queries/drift/q82.sql           |  5 ++
 .../predicate_eval/queries/drift/q83.sql           |  5 ++
 .../predicate_eval/queries/neutral/q60.sql         |  3 +-
 .../predicate_eval/queries/neutral/q61.sql         |  4 +-
 .../predicate_eval/queries/nulls/q90.sql           |  6 ++
 .../predicate_eval/queries/nulls/q91.sql           |  5 ++
 .../predicate_eval/queries/scale/q50.sql           |  6 --
 .../predicate_eval/queries/scale/q51.sql           |  4 -
 .../predicate_eval/queries/scale/q52.sql           |  4 -
 .../predicate_eval/queries/scale/q53.sql           |  4 -
 .../predicate_eval/queries/selectivity/q20.sql     |  5 +-
 .../predicate_eval/queries/selectivity/q21.sql     |  3 +-
 .../predicate_eval/queries/width/q40.sql           |  9 --
 .../predicate_eval/queries/width/q41.sql           |  7 --
 .../predicate_eval/queries/width/q42.sql           |  7 --
 .../predicate_eval/results/cardinality_q30_k2.csv  |  2 +
 .../predicate_eval/results/cardinality_q31_k4.csv  |  2 +
 .../predicate_eval/results/cardinality_q32_k8.csv  |  2 +
 .../predicate_eval/results/cardinality_q33_k16.csv |  2 +
 .../results/cardinality_q34_k8_wide64.csv          |  2 +
 .../results/correlation_q70_independent.csv        |  2 +
 .../results/correlation_q71_positive.csv           |  2 +
 .../results/correlation_q72_anti.csv               |  2 +
 .../results/correlation_q73_redundant_proxy.csv    |  2 +
 .../results/cost_q10_expensive_first.csv           |  2 +
 .../results/cost_q11_cheap_first.csv               |  2 +
 .../results/costsel_q01_regexp_selective_last.csv  |  2 +
 .../results/costsel_q02_regexp_selective_first.csv |  2 +
 ..._cheap_unselective_then_expensive_selective.csv |  2 +
 ..._expensive_selective_then_cheap_unselective.csv |  2 +
 .../predicate_eval/results/drift_q80_a_then_b.csv  |  2 +
 .../predicate_eval/results/drift_q82_late_flip.csv |  2 +
 .../results/drift_q83_per_partition_skew.csv       |  2 +
 .../results/neutral_q60_cheap_uniform.csv          |  2 +
 .../results/neutral_q61_expensive_uniform.csv      |  2 +
 .../results/nulls_q90_nullable_selective_first.csv |  2 +
 .../results/nulls_q91_nullable_selective_last.csv  |  2 +
 .../predicate_eval/results/scale_q50_5k.csv        |  2 +
 .../predicate_eval/results/scale_q51_100k.csv      |  2 +
 .../predicate_eval/results/scale_q52_5m.csv        |  2 +
 .../predicate_eval/results/scale_q53_50m.csv       |  2 +
 .../results/selectivity_q20_unselective_first.csv  |  2 +
 .../results/selectivity_q21_selective_first.csv    |  2 +
 .../predicate_eval/results/width_q40_narrow.csv    |  2 +
 .../predicate_eval/results/width_q41_wide.csv      |  2 +
 .../predicate_eval/results/width_q42_xwide.csv     |  2 +
 .../predicate_eval/scratch/.gitignore              |  1 +
 108 files changed, 393 insertions(+), 276 deletions(-)

diff --git a/benchmarks/bench.sh b/benchmarks/bench.sh
index 419fd5be3a..df33d2aec7 100755
--- a/benchmarks/bench.sh
+++ b/benchmarks/bench.sh
@@ -106,7 +106,7 @@ wide_schema:            Small-projection queries on a wide 
synthetic dataset (10
                           (runs both 'wide' and 'narrow' subgroups: narrow is 
an internal baseline; the wide-vs-narrow ratio is the signal)
 predicate_eval:         Conjunctive (AND) filter-evaluation micro-benchmarks; 
each subgroup is a different predicate pattern, to test how an
                           adaptive predicate-ordering system behaves across 
them (see https://github.com/apache/datafusion/issues/11262)
-                          (subgroups via BENCH_SUBGROUP: costsel, cost, 
selectivity, cardinality, width, scale, neutral, correlation, drift)
+                          (subgroups via BENCH_SUBGROUP: costsel, cost, 
selectivity, cardinality, width, scale, neutral, correlation, drift, nulls)
                           (toggle a system under test with its native 
DATAFUSION_* env var; size data with PRED_ROWS, string width with PRED_FILL)
 parquet_row_filter_skip: Per-RG fully-matched RowFilter skip on Parquet 
(apache/datafusion#23696); clustered string key + low-selectivity
                           range filter + pushdown, so most row groups are 
fully matched and the per-row RowFilter is skipped on them
@@ -876,7 +876,11 @@ run_push_down_topk() {
 # micro-benchmarks where each subgroup is a different predicate pattern, used 
to
 # test how an adaptive predicate-ordering system behaves across them (see
 # https://github.com/apache/datafusion/issues/11262). Data is generated inline
-# by the suite's load SQL, so there is no data step.
+# by the suite's load SQL, so there is no data step (drift q82 and q83 share 16
+# small Parquet files written into sql_benchmarks/predicate_eval/scratch/, 
which
+# is gitignored; q82 reads them with target_partitions=1, so the selectivity 
flip
+# lands halfway through one stream, and q83 with 16, so each stream gets one 
whole
+# file and a fixed profile).
 #
 # By default the suite measures DataFusion's built-in left-deep AND 
short-circuit
 # and sets no engine config of its own. To evaluate a system under test, export
@@ -885,7 +889,7 @@ run_push_down_topk() {
 #   DATAFUSION_EXECUTION_ADAPTIVE_FILTER_REORDERING=true ./bench.sh run 
predicate_eval
 # Suite-specific knobs (string-substituted into the load SQL, not engine 
config):
 #   BENCH_SUBGROUP   run one subgroup (costsel, cost, selectivity, cardinality,
-#                    width, scale, neutral, correlation, drift)
+#                    width, scale, neutral, correlation, drift, nulls)
 #   PRED_ROWS        synthetic row count (default 1_000_000; the scale subgroup
 #                    overrides this per query)
 #   PRED_FILL        filler chars per marker = string-column width knob
diff --git a/benchmarks/sql_benchmarks/README.md 
b/benchmarks/sql_benchmarks/README.md
index 1ce1fa488c..b8bf8adede 100644
--- a/benchmarks/sql_benchmarks/README.md
+++ b/benchmarks/sql_benchmarks/README.md
@@ -43,7 +43,7 @@ in the community:
 | `tpcds`               | TPC‑DS queries                                       
              |
 | `tpch`                | TPC‑H queries                                        
              |
 | `wide_schema`         | Small-projection queries on a wide (1024-col, 
256-file) synthetic dataset; runs `wide` + `narrow` subgroups for comparison |
-| `predicate_eval`      | Conjunctive (AND) filter-evaluation 
micro-benchmarks; each subgroup is a different predicate pattern, to test how 
an adaptive predicate-ordering system behaves across them 
([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups 
(`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, 
`scale`, `neutral`, `correlation`, `drift`. Configure the system under test 
through its DataFusion settings. |
+| `predicate_eval`      | Conjunctive (AND) filter-evaluation 
micro-benchmarks; each subgroup is a different predicate pattern, to test how 
an adaptive predicate-ordering system behaves across them 
([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups 
(`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, 
`scale`, `neutral`, `correlation`, `drift`, `nulls`. The suite sets no engine 
config of its own, so by default it measures DataFusion's built-in le [...]
 | `parquet_row_filter_skip` | Micro-benchmark for the per-row-group 
fully-matched RowFilter skip on Parquet scans 
([#23696](https://github.com/apache/datafusion/issues/23696)). Subgroups 
(`--subgroup`): `skip` (clustered key, most row groups fully matched by 
statistics so the per-row filter is skipped), `control` (scrambled key, no row 
group is ever fully matched). Size the data with `PRED_ROWS` and the row-group 
size with `RG_SIZE`. |
 
 # Running Benchmarks
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark
index 760ea2ca90..d404a193d4 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark
@@ -1,7 +1,6 @@
 subgroup cardinality
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cardinality
-QPAD=30
+QUERY=cardinality/q30
 DATASET=ints
 NAME=cardinality_q30_k2
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark
index 74f22715d1..54a25337bd 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark
@@ -1,7 +1,6 @@
 subgroup cardinality
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cardinality
-QPAD=31
+QUERY=cardinality/q31
 DATASET=ints
 NAME=cardinality_q31_k4
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark
index b6b69c3852..aff0173e3d 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark
@@ -1,7 +1,6 @@
 subgroup cardinality
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cardinality
-QPAD=32
+QUERY=cardinality/q32
 DATASET=ints
 NAME=cardinality_q32_k8
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark
index 1260e68137..bb1464d200 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark
@@ -1,7 +1,6 @@
 subgroup cardinality
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cardinality
-QPAD=33
+QUERY=cardinality/q33
 DATASET=ints
 NAME=cardinality_q33_k16
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark
similarity index 57%
copy from 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark
copy to 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark
index b6b69c3852..c1851e34ad 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark
@@ -1,7 +1,6 @@
 subgroup cardinality
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cardinality
-QPAD=32
-DATASET=ints
-NAME=cardinality_q32_k8
+QUERY=cardinality/q34
+DATASET=ints_wide
+NAME=cardinality_q34_k8_wide64
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark
index ef20f7dc49..2084ddab97 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark
@@ -1,7 +1,6 @@
 subgroup correlation
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=correlation
-QPAD=70
+QUERY=correlation/q70
 DATASET=corr
 NAME=correlation_q70_independent
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark
index 8875f6c44e..cd5e63aa95 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark
@@ -1,7 +1,6 @@
 subgroup correlation
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=correlation
-QPAD=71
+QUERY=correlation/q71
 DATASET=corr
 NAME=correlation_q71_positive
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark
index 8109f1439a..e1dfc3fdaf 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark
@@ -1,7 +1,6 @@
 subgroup correlation
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=correlation
-QPAD=72
+QUERY=correlation/q72
 DATASET=corr
 NAME=correlation_q72_anti
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark
index cc3f7bcf54..c9a89b1da8 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark
@@ -1,7 +1,6 @@
 subgroup correlation
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=correlation
-QPAD=73
+QUERY=correlation/q73
 DATASET=corrproxy
 NAME=correlation_q73_redundant_proxy
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark
index 9b864b8594..57c7034573 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark
@@ -1,7 +1,6 @@
 subgroup cost
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cost
-QPAD=10
+QUERY=cost/q10
 DATASET=mixed
 NAME=cost_q10_expensive_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark
index 296ea443b3..30398925cb 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark
@@ -1,7 +1,6 @@
 subgroup cost
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=cost
-QPAD=11
+QUERY=cost/q11
 DATASET=mixed
 NAME=cost_q11_cheap_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark
index abedd1d580..123839a2de 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark
@@ -1,7 +1,6 @@
 subgroup costsel
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=costsel
-QPAD=01
+QUERY=costsel/q01
 DATASET=markers
 NAME=costsel_q01_regexp_selective_last
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark
index f50aab6642..304e67189d 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark
@@ -1,7 +1,6 @@
 subgroup costsel
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=costsel
-QPAD=02
+QUERY=costsel/q02
 DATASET=markers
 NAME=costsel_q02_regexp_selective_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark
index 10c4ce184e..d039e6e28d 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark
@@ -1,7 +1,6 @@
 subgroup costsel
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=costsel
-QPAD=03
+QUERY=costsel/q03
 DATASET=mixed
 NAME=costsel_q03_cheap_unselective_then_expensive_selective
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark
similarity index 55%
copy from 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark
copy to 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark
index 10c4ce184e..811e5d642a 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark
@@ -1,7 +1,6 @@
 subgroup costsel
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=costsel
-QPAD=03
+QUERY=costsel/q04
 DATASET=mixed
-NAME=costsel_q03_cheap_unselective_then_expensive_selective
+NAME=costsel_q04_expensive_selective_then_cheap_unselective
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark
index 970adc53f8..fcbd32b0bf 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark
@@ -1,7 +1,6 @@
 subgroup drift
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=drift
-QPAD=80
+QUERY=drift/q80
 DATASET=drift
 NAME=drift_q80_a_then_b
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark
deleted file mode 100644
index 93cde75ffe..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark
+++ /dev/null
@@ -1,7 +0,0 @@
-subgroup drift
-
-template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=drift
-QPAD=81
-DATASET=drift
-NAME=drift_q81_b_then_a
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark
new file mode 100644
index 0000000000..8ec68bd5c1
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark
@@ -0,0 +1,10 @@
+subgroup drift
+
+template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
+QUERY=drift/q82
+DATASET=drift_split
+NAME=drift_q82_late_flip
+
+# One stream over the whole directory: the flip lands mid-scan.
+init
+set datafusion.execution.target_partitions = 1;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark
new file mode 100644
index 0000000000..be25d56612
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark
@@ -0,0 +1,10 @@
+subgroup drift
+
+template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
+QUERY=drift/q83
+DATASET=drift_split
+NAME=drift_q83_per_partition_skew
+
+# One file per stream: each stream sees a single profile.
+init
+set datafusion.execution.target_partitions = 16;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
index 039fee622b..5d39993e98 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
@@ -1,7 +1,6 @@
 subgroup neutral
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=neutral
-QPAD=60
+QUERY=neutral/q60
 DATASET=ints
 NAME=neutral_q60_cheap_uniform
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark
index edaf89b471..ec66cdfc39 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark
@@ -1,7 +1,6 @@
 subgroup neutral
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=neutral
-QPAD=61
+QUERY=neutral/q61
 DATASET=markers
 NAME=neutral_q61_expensive_uniform
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark
similarity index 54%
copy from 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
copy to benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark
index 039fee622b..4e82661a56 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark
@@ -1,7 +1,6 @@
-subgroup neutral
+subgroup nulls
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=neutral
-QPAD=60
+QUERY=nulls/q90
 DATASET=ints
-NAME=neutral_q60_cheap_uniform
+NAME=nulls_q90_nullable_selective_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark
similarity index 54%
copy from 
benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
copy to benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark
index 039fee622b..377b139bf0 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark
@@ -1,7 +1,6 @@
-subgroup neutral
+subgroup nulls
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=neutral
-QPAD=60
+QUERY=nulls/q91
 DATASET=ints
-NAME=neutral_q60_cheap_uniform
+NAME=nulls_q91_nullable_selective_last
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark
index 0bef31e14f..1c8f2c148f 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark
@@ -1,8 +1,7 @@
 subgroup scale
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=scale
-QPAD=50
+QUERY=costsel/q03
 DATASET=mixed
 PRED_ROWS=5000
 NAME=scale_q50_5k
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark
index 8f1315fb11..52bd2a0fd0 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark
@@ -1,8 +1,7 @@
 subgroup scale
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=scale
-QPAD=51
+QUERY=costsel/q03
 DATASET=mixed
 PRED_ROWS=100000
 NAME=scale_q51_100k
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark
index 7ddbfc19b4..9b37a48627 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark
@@ -1,8 +1,7 @@
 subgroup scale
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=scale
-QPAD=52
+QUERY=costsel/q03
 DATASET=mixed
 PRED_ROWS=5000000
 NAME=scale_q52_5m
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark
index 6cea5c44a1..9839289113 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark
@@ -1,8 +1,7 @@
 subgroup scale
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=scale
-QPAD=53
+QUERY=costsel/q03
 DATASET=mixed
 PRED_ROWS=50000000
 NAME=scale_q53_50m
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark
index 077a62650d..f587dc50d9 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark
@@ -1,7 +1,6 @@
 subgroup selectivity
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=selectivity
-QPAD=20
+QUERY=selectivity/q20
 DATASET=ints
 NAME=selectivity_q20_unselective_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark
index 24fc6ef4cd..655b067211 100644
--- 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark
@@ -1,7 +1,6 @@
 subgroup selectivity
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=selectivity
-QPAD=21
+QUERY=selectivity/q21
 DATASET=ints
 NAME=selectivity_q21_selective_first
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark
index df66cf16a3..186fafc40b 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark
@@ -1,8 +1,7 @@
 subgroup width
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=width
-QPAD=40
+QUERY=costsel/q01
 DATASET=markers
 PRED_FILL=2
 NAME=width_q40_narrow
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark
index c260dc9985..16967c4d87 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark
@@ -1,8 +1,7 @@
 subgroup width
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=width
-QPAD=41
+QUERY=costsel/q01
 DATASET=markers
 PRED_FILL=30
 NAME=width_q41_wide
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark 
b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark
index 988ff59c70..44ebe3137d 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark
+++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark
@@ -1,8 +1,7 @@
 subgroup width
 
 template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
-SUBGROUP=width
-QPAD=42
+QUERY=costsel/q01
 DATASET=markers
 PRED_FILL=170
 NAME=width_q42_xwide
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql
index 2d7ceb73e6..946a999ceb 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql
@@ -1,15 +1,6 @@
--- Correlation dataset: a base column plus derived columns that control the
--- *conditional* selectivity of one predicate given another (its selectivity
--- among the rows that already passed the other).
---
---   x       uniform [0,100)
---   x_pos   = x         (perfectly positively correlated: `x<k AND x_pos<k`
---                        passes ~k%, not the ~k%^2 an independence assumption
---                        predicts)
---   x_anti  = 99 - x    (anti-correlated: `x<k AND x_anti<k` is empty for 
k<=50)
---   ind     independent control column, uniform [0,100)
---
--- PRED_ROWS sizes the table.
+-- Correlation dataset: `x` uniform on [0,100); `x_pos` = x (perfectly 
positively
+-- correlated); `x_anti` = 99 - x (anti-correlated); `ind` uniform on [0,100) 
and
+-- independent of x. PRED_ROWS sizes the table.
 CREATE TABLE t AS
 SELECT
   (value * 7)  % 100        AS x,
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/corrproxy.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/corrproxy.sql
index f06e68d38c..e17d0d4925 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/corrproxy.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/corrproxy.sql
@@ -1,30 +1,20 @@
--- Correlated-proxy dataset: a cheap integer predicate that is a perfect proxy
--- for three string predicates, plus one independent string predicate.
---
+-- Correlated-proxy dataset:
 --   c0          = 1 for ~30% of rows (cheap proxy)
---   s1, s2, s3  each contain a marker exactly where c0 = 1     (correlated)
---   s4          contains a marker for an independent ~30%      (independent)
---
--- The four string columns are deliberately *identical in shape*: same width,
--- the same single marker at the same offset, each matched by an equally cheap
--- regex with the same ~30% marginal selectivity. Marginally the four regex
--- predicates are therefore indistinguishable -- same cost, same selectivity, 
in
--- every position -- so neither a marginal cost/selectivity estimator nor
--- runtime timing can prefer one over another. Only their *conditional*
--- behaviour behind the proxy differs: after `c0 = 1`, the s1/s2/s3 regexes 
keep
--- every survivor (each re-tests the proxy's condition) while the s4 regex 
still
--- discards ~70%. Only joint statistics can see that; an independence 
assumption
--- prices all four regexes identically in every position.
+--   s1, s2, s3  each carry a marker exactly where c0 = 1  (correlated)
+--   s4          carries a marker for an independent ~30%  (independent)
+-- PRED_FILL sets the filler width each side of the marker, PRED_ROWS the row 
count.
 --
--- PRED_FILL sets the filler width on each side of the marker (a non-matching
--- `regexp_like` must scan the whole value), and PRED_ROWS sizes the table.
+-- The four string columns are identical in shape -- same width, same marker
+-- offset, same regex cost, same ~30% marginal selectivity -- so marginally 
they
+-- are indistinguishable in every position. Only their joint distribution with 
the
+-- proxy differs: after `c0 = 1` the s1/s2/s3 regexes keep every survivor 
while s4
+-- still discards ~70%. Ranking them therefore takes joint statistics; an
+-- independence assumption prices all four regexes identically everywhere.
 CREATE TABLE t AS
 WITH base AS (
   SELECT
-    -- The cheap proxy and the independent control share one definition each, 
so
-    -- the perfect-proxy / independence invariants can't drift apart silently.
-    (value * 7)  % 100 < 30 AS proxy,   -- ~30%, drives c0 and s1/s2/s3
-    (value * 13) % 100 < 30 AS indep    -- ~30%, independent of proxy, drives 
s4
+    (value * 7)  % 100 < 30 AS proxy,
+    (value * 13) % 100 < 30 AS indep
   FROM generate_series(1, ${PRED_ROWS:-1000000})
 )
 SELECT
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql
index d5635d91dd..0345abc3f2 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql
@@ -1,16 +1,11 @@
--- Drift dataset: two predicates whose *relative* selectivity flips partway
--- through the scan, so whole-table selectivity differs from per-batch
--- selectivity. Rows are emitted in `seq` order, so batches observe the drift 
in
--- order.
---
---   a_sel = 0  is selective (~0.1%) in the first 10% of rows, unselective
---              (~50%) afterwards.
---   b_sel = 0  is the mirror: unselective early, selective late.
---
--- PRED_ROWS sizes the table.
+-- Drift dataset: rows are emitted in `seq` order and which predicate is more
+-- selective flips partway through, so whole-table selectivity differs from
+-- per-batch selectivity. `a_sel = 0` matches ~0.1% over the first 2% of rows 
and
+-- ~50% after; `b_sel = 0` is the mirror. At the default PRED_ROWS, which 
sizes the
+-- table, that flip lands inside the pooled 8-batch warm-up (~65k rows).
 CREATE TABLE t AS
 SELECT
   value AS seq,
-  CASE WHEN value < ${PRED_ROWS:-1000000} / 10 THEN value % 1000 ELSE value % 
2    END AS a_sel,
-  CASE WHEN value < ${PRED_ROWS:-1000000} / 10 THEN value % 2    ELSE value % 
1000 END AS b_sel
+  CASE WHEN value < ${PRED_ROWS:-1000000} / 50 THEN value % 1000 ELSE value % 
2    END AS a_sel,
+  CASE WHEN value < ${PRED_ROWS:-1000000} / 50 THEN value % 2    ELSE value % 
1000 END AS b_sel
 FROM generate_series(1, ${PRED_ROWS:-1000000});
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql
new file mode 100644
index 0000000000..b36a0d122f
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql
@@ -0,0 +1,96 @@
+-- Split drift dataset, shared by q82 and q83: 16 Parquet files of PRED_ROWS / 
16
+-- rows each, at the q80 rates -- f00..f07 carry profile A (`a_sel = 0` 
selective
+-- at ~0.1%, `b_sel = 0` unselective at ~50%) and f08..f15 the mirror.
+--
+-- One `COPY` per file, because a directory-target `COPY` names its output 
with a
+-- random write id and Parquet has no overwrite, so a second load would add 
files;
+-- the names are zero-padded so they sort numerically. `FileGroup::split_files`
+-- sorts a group's files by path, so `target_partitions = 1` (q82) reads 
f00..f15
+-- in order and `= 16` (q83) gives each stream one whole file; each query sets 
it.
+-- `repartition_file_scans` stays off, or both profiles land in one stream 
again.
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f00.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f01.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f02.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f03.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f04.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f05.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f06.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2    AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f07.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f08.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f09.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f10.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f11.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f12.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f13.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f14.parquet'
+STORED AS PARQUET;
+
+COPY (SELECT value AS seq, value % 2    AS a_sel, value % 1000 AS b_sel
+      FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value)
+TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f15.parquet'
+STORED AS PARQUET;
+
+set datafusion.optimizer.repartition_file_scans = false;
+
+CREATE EXTERNAL TABLE t
+STORED AS PARQUET
+LOCATION 'sql_benchmarks/predicate_eval/scratch/drift_split/';
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/ints.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/ints.sql
index 1d3f030915..f36b283c2a 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/ints.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/ints.sql
@@ -1,8 +1,8 @@
--- Sixteen independent integer columns, each uniform on [0,100). The predicate
--- `cN < k` therefore has selectivity ~k%. All columns are equally cheap to
--- evaluate, so only selectivity (not cost) distinguishes orderings here. The
--- multipliers are all coprime to 100, which keeps the residues uniform and the
--- columns mutually decorrelated. PRED_ROWS sizes the table.
+-- Seventeen integer columns: c0..c15 uniform on [0,100), so `cN < k` has
+-- selectivity ~k%, all equally cheap and mutually decorrelated (the 
multipliers
+-- are coprime to 100); plus `c_sel`, NULL on 10% of rows and uniform on 
[0,100)
+-- elsewhere, so `c_sel < 5` is true on 4% of rows and NULL on 10% -- a 
selective
+-- conjunct that is not NULL-free. PRED_ROWS sizes the table.
 CREATE TABLE t AS
 SELECT
   (value * 1)  % 100 AS c0,
@@ -20,5 +20,6 @@ SELECT
   (value * 31) % 100 AS c12,
   (value * 33) % 100 AS c13,
   (value * 37) % 100 AS c14,
-  (value * 39) % 100 AS c15
+  (value * 39) % 100 AS c15,
+  CASE WHEN value % 10 = 0 THEN NULL ELSE (value * 11) % 100 END AS c_sel
 FROM generate_series(1, ${PRED_ROWS:-1000000});
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql
new file mode 100644
index 0000000000..cddce6f8e5
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql
@@ -0,0 +1,71 @@
+-- `ints.sql` widened to 64 integer columns, each uniform on [0,100), so `cN < 
k`
+-- again has selectivity ~k%. c0..c15 reuse the same multipliers, so a 
predicate
+-- over them has exactly the same selectivities as on `ints` and only the 
width of
+-- the batches flowing through the filter changes. PRED_ROWS sizes the table.
+CREATE TABLE t AS
+SELECT
+  (value * 1)   % 100 AS c0,
+  (value * 3)   % 100 AS c1,
+  (value * 7)   % 100 AS c2,
+  (value * 9)   % 100 AS c3,
+  (value * 11)  % 100 AS c4,
+  (value * 13)  % 100 AS c5,
+  (value * 17)  % 100 AS c6,
+  (value * 19)  % 100 AS c7,
+  (value * 21)  % 100 AS c8,
+  (value * 23)  % 100 AS c9,
+  (value * 27)  % 100 AS c10,
+  (value * 29)  % 100 AS c11,
+  (value * 31)  % 100 AS c12,
+  (value * 33)  % 100 AS c13,
+  (value * 37)  % 100 AS c14,
+  (value * 39)  % 100 AS c15,
+  (value * 41)  % 100 AS c16,
+  (value * 43)  % 100 AS c17,
+  (value * 47)  % 100 AS c18,
+  (value * 49)  % 100 AS c19,
+  (value * 51)  % 100 AS c20,
+  (value * 53)  % 100 AS c21,
+  (value * 57)  % 100 AS c22,
+  (value * 59)  % 100 AS c23,
+  (value * 61)  % 100 AS c24,
+  (value * 63)  % 100 AS c25,
+  (value * 67)  % 100 AS c26,
+  (value * 69)  % 100 AS c27,
+  (value * 71)  % 100 AS c28,
+  (value * 73)  % 100 AS c29,
+  (value * 77)  % 100 AS c30,
+  (value * 79)  % 100 AS c31,
+  (value * 81)  % 100 AS c32,
+  (value * 83)  % 100 AS c33,
+  (value * 87)  % 100 AS c34,
+  (value * 89)  % 100 AS c35,
+  (value * 91)  % 100 AS c36,
+  (value * 93)  % 100 AS c37,
+  (value * 97)  % 100 AS c38,
+  (value * 99)  % 100 AS c39,
+  (value * 101) % 100 AS c40,
+  (value * 103) % 100 AS c41,
+  (value * 107) % 100 AS c42,
+  (value * 109) % 100 AS c43,
+  (value * 111) % 100 AS c44,
+  (value * 113) % 100 AS c45,
+  (value * 117) % 100 AS c46,
+  (value * 119) % 100 AS c47,
+  (value * 121) % 100 AS c48,
+  (value * 123) % 100 AS c49,
+  (value * 127) % 100 AS c50,
+  (value * 129) % 100 AS c51,
+  (value * 131) % 100 AS c52,
+  (value * 133) % 100 AS c53,
+  (value * 137) % 100 AS c54,
+  (value * 139) % 100 AS c55,
+  (value * 141) % 100 AS c56,
+  (value * 143) % 100 AS c57,
+  (value * 147) % 100 AS c58,
+  (value * 149) % 100 AS c59,
+  (value * 151) % 100 AS c60,
+  (value * 153) % 100 AS c61,
+  (value * 157) % 100 AS c62,
+  (value * 159) % 100 AS c63
+FROM generate_series(1, ${PRED_ROWS:-1000000});
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/markers.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/markers.sql
index 6b8fff9385..d1c818fe0f 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/markers.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/markers.sql
@@ -1,15 +1,8 @@
--- Wide-string dataset: five markers embedded in `PRED_FILL`-wide filler so 
that a
--- non-matching `regexp_like` must scan the whole value (every string predicate
--- is "expensive"). Selectivities are coprime so the predicates are 
independent:
---
---   'aaa'  present in ~90%  of rows  (value % 10  <> 0)
---   'bbb'  present in ~86%  of rows  (value % 7   <> 0)
---   'ccc'  present in ~80%  of rows  (value % 5   <> 0)
---   'ddd'  present in ~75%  of rows  (value % 4   <> 0)
---   'rare' present in ~0.1% of rows  (value % 1009 = 5)   <- the selective one
---
--- PRED_FILL sets the filler width per marker (the string-column width knob: 
~6*PRED_FILL
--- chars per row), and PRED_ROWS sizes the table.
+-- Wide-string dataset: one column `s` holding five markers in PRED_FILL-wide
+-- filler, so a non-matching `regexp_like` must scan the whole value. 'aaa' 
~90%,
+-- 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%, 'rare' ~0.1%; the moduli are coprime, 
so the
+-- markers are independent. PRED_FILL is the string-width knob (~6*PRED_FILL 
chars
+-- per row) and PRED_ROWS sizes the table.
 CREATE TABLE t AS
 SELECT
   repeat('q', ${PRED_FILL:-30})
diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql 
b/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql
index a51c1040da..bc931e8a40 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql
@@ -1,15 +1,7 @@
--- Mixed-cost dataset: cheap integer columns (`cN < k` ~ k% selectivity)
--- alongside one wide string column carrying three markers matched by expensive
--- `regexp_like`:
---
---   'rare' present in ~0.1% of rows  (value % 1009 = 5)
---   'ten'  present in ~10%  of rows  (value % 10   = 0)
---   'aaa'  present in ~90%  of rows  (value % 10  <> 0)
---
--- This lets a single table mix cheap integer compares with expensive regexp
--- scans at independently chosen selectivities (e.g. a cheap, unselective 
compare
--- next to an expensive, selective regexp). PRED_FILL is the string-width knob;
--- PRED_ROWS sizes the table.
+-- Mixed-cost dataset: cheap integer columns c0..c3 uniform on [0,100) (`cN < 
k`
+-- ~k%) alongside one wide string column `s` carrying three markers matched by 
an
+-- expensive `regexp_like`: 'rare' ~0.1%, 'ten' ~10%, 'aaa' ~90%. PRED_FILL is 
the
+-- string-width knob and PRED_ROWS sizes the table.
 CREATE TABLE t AS
 SELECT
   (value * 1)  % 100 AS c0,
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template 
b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
index e2e3d6175e..766bd20270 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
+++ b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template
@@ -1,35 +1,22 @@
-# Shared template for every predicate_eval benchmark. Each qNN.benchmark sets
-# its `subgroup` directive and then includes this template with parameters:
-#   SUBGROUP  subgroup name, also the query sub-directory          (e.g. 
costsel)
-#   QPAD      zero-padded query id, also the query file stem       (e.g. 01)
-#   DATASET   load script stem under load/                         (e.g. 
markers)
-#   NAME      criterion display name                               (e.g. 
costsel_q01_regexp_selective_last)
-# Optional (consumed by the load scripts via ${...:-default}):
-#   PRED_ROWS      synthetic row count                                  
(default 1_000_000)
-#   PRED_FILL      filler chars per marker = string-column width knob    
(default 30)
-#
-# The run SQL lives in queries/${SUBGROUP}/q${QPAD}.sql so the WHERE clause is
-# readable on its own. The table is always named `t`, so the assert and cleanup
-# are uniform across datasets.
-#
-# The suite is implementation-agnostic and sets no engine config of its own: it
-# measures DataFusion's built-in left-deep `AND` short-circuit by default. To
-# evaluate a predicate-ordering system under test, set its native config via 
the
-# environment (the bench harness builds its SessionContext with
-# SessionConfig::from_env), e.g.
-#   DATAFUSION_EXECUTION_ADAPTIVE_FILTER_REORDERING=true
+# Shared template for every predicate_eval benchmark. Each qNN.benchmark sets 
its
+# `subgroup` directive and includes this template with parameters:
+#   QUERY     query file stem under queries/  (e.g. costsel/q01)
+#   DATASET   load script stem under load/    (e.g. markers)
+#   NAME      criterion display name          (e.g. 
costsel_q01_regexp_selective_last)
+# Optional, read by the load scripts as ${...:-default}:
+#   PRED_ROWS  synthetic row count                          (default 1000000)
+#   PRED_FILL  filler chars per marker, the string-width knob (default 30)
+# The knob sweeps reuse another subgroup's query file through QUERY, and the 
table
+# is always named `t`, so cleanup is uniform across datasets.
 
 load sql_benchmarks/predicate_eval/load/${DATASET}.sql
 
 name ${NAME}
 group predicate_eval
 
-assert I
-SELECT count(*) > 0 FROM t;
-----
-true
+run sql_benchmarks/predicate_eval/queries/${QUERY}.sql
 
-run sql_benchmarks/predicate_eval/queries/${SUBGROUP}/q${QPAD}.sql
+result sql_benchmarks/predicate_eval/results/${NAME}.csv
 
 result sql_benchmarks/predicate_eval/results/${NAME}.csv
 
diff --git a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite 
b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite
index af1a326cd8..4ee33994d3 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite
+++ b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite
@@ -1,4 +1,4 @@
-description = "Conjunctive filter evaluation micro-benchmarks covering 
predicate cost, selectivity, cardinality, width, scale, correlation, and drift"
+description = "Conjunctive filter evaluation micro-benchmarks covering 
predicate cost, selectivity, cardinality, width, scale, correlation, drift, and 
nullable predicates"
 
 query_pattern = "q{QUERY_ID_PADDED}.benchmark"
 
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql
index 3be840e917..dbd410f957 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql
@@ -1,6 +1,4 @@
--- Hidden: cheap integer compares; `c1 < 5` matches ~5%, the `c0 < 90` family
--- ~90%. k = 2 here. q30..q33 sweep k = 2/4/8/16 with one ~5% predicate written
--- last among ~90% ones.
+-- k = 2: one ~90% compare then one ~5% compare (q30..q33 sweep k = 2/4/8/16).
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND c1 < 5;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql
index 4ba84f8124..d21dd6273e 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql
@@ -1,4 +1,4 @@
--- k = 4: three ~90% compares followed by one ~5% compare. See q30.
+-- k = 4: three ~90% compares then one ~5% compare.
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND c1 < 90
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql
index d9e920cc62..83037bdfc5 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql
@@ -1,4 +1,4 @@
--- k = 8: seven ~90% compares followed by one ~5% compare. See q30.
+-- k = 8: seven ~90% compares then one ~5% compare.
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND c1 < 90
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql
index 2408427ab7..890ff4205d 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql
@@ -1,4 +1,4 @@
--- k = 16: fifteen ~90% compares followed by one ~5% compare. See q30.
+-- k = 16: fifteen ~90% compares then one ~5% compare.
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND c1 < 90
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql
similarity index 66%
copy from benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql
copy to benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql
index d9e920cc62..2c5b586e54 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql
@@ -1,4 +1,4 @@
--- k = 8: seven ~90% compares followed by one ~5% compare. See q30.
+-- k = 8 as in q32, but over the 64-column `ints_wide` table.
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND c1 < 90
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql
index 86e33534c7..f9d7017e61 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql
@@ -1,6 +1,4 @@
--- Hidden: `x` and `ind` are independent, each ~20%, so the conjunction matches
--- ~4% and the second predicate is just as selective among the first's 
survivors
--- as on its own. Baseline for the correlation sweep. cf. q71, q72.
+-- Independent: `x` and `ind` are uncorrelated, each ~20%, so the pair matches 
~4%.
 SELECT count(*) FROM t
 WHERE x < 20
   AND ind < 20;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql
index eda61cc289..4b5925e80a 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql
@@ -1,6 +1,4 @@
--- Hidden: `x_pos` is a copy of `x`, so `x < 20 AND x_pos < 20` still matches
--- ~20% (not the ~4% independence would imply) -- the second predicate removes
--- none of the first's survivors. cf. q70.
+-- Positively correlated: `x_pos` is a copy of `x`, so the pair still matches 
~20%.
 SELECT count(*) FROM t
 WHERE x < 20
   AND x_pos < 20;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql
index ff987524da..63ec8d57de 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql
@@ -1,6 +1,4 @@
--- Hidden: `x_anti` is `99 - x`, so `x < 50 AND x_anti < 50` is empty -- the
--- second predicate removes all of the first's survivors, though each matches
--- ~50% alone. cf. q70.
+-- Anti-correlated: `x_anti` is `99 - x`, so the pair is empty though each 
matches ~50%.
 SELECT count(*) FROM t
 WHERE x < 50
   AND x_anti < 50;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql
index 5e1e822e92..6dc76a8cab 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql
@@ -1,11 +1,5 @@
--- Hidden: `c0 = 1` is a perfect proxy for the s1/s2/s3 regexes -- after the
--- cheap proxy, each of those keeps every survivor while the equally selective
--- (~30%) s4 regex still discards ~70%. The optimal order is [c0, s4, s1/s2/s3]
--- (one informative regex on 30% of rows, the three redundant ones on 9%), but
--- the four regexes are marginally identical -- same width, same marker offset,
--- same cost, same selectivity -- so ranking them takes their *joint*
--- distribution with the proxy. Written with the redundant regexes first,
--- grouped with their proxy, as an author naturally would.
+-- Redundant proxy: `c0 = 1` implies the s1/s2/s3 regexes, while the marginally
+-- identical s4 regex is independent of it. Written proxy-first, regexes 
grouped.
 SELECT count(*) FROM t
 WHERE c0 = 1
   AND regexp_like(s1, 'a.a')
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql
index b089ebc7a1..cbe0e937ef 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql
@@ -1,6 +1,4 @@
--- Hidden: both predicates match ~10%, but `regexp_like(s, 'ten')` scans the
--- string (expensive) while `c0 < 10` is a cheap compare. Equal selectivity,
--- unequal cost; expensive one written first. cf. q11 (opposite order).
+-- Equal selectivity (~10%), unequal cost: the expensive regexp written first. 
cf. q11.
 SELECT count(*) FROM t
 WHERE regexp_like(s, 'ten')
   AND c0 < 10;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql
index 82d748c93b..54167ff194 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql
@@ -1,5 +1,4 @@
--- Same two predicates as q10 (both ~10%; regexp expensive, compare cheap),
--- opposite written order. cf. q10.
+-- q10 with the cheap compare written first.
 SELECT count(*) FROM t
 WHERE c0 < 10
   AND regexp_like(s, 'ten');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql
index bc029ed5d8..1618c54814 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql
@@ -1,7 +1,5 @@
--- Hidden in the data: the five markers have very different selectivities --
--- 'aaa' ~90%, 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%, 'rare' ~0.1% -- while every
--- regexp_like costs about the same. 'rare' (most selective) is written last.
--- cf. q02 (most selective written first).
+-- Five equally expensive regexps of very different selectivity ('rare' ~0.1%, 
the
+-- rest 75-90%), the selective one written last. cf. q02.
 SELECT count(*) FROM t
 WHERE regexp_like(s, 'aaa')
   AND regexp_like(s, 'bbb')
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql
index 7f7fc61831..dc57aa8d88 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql
@@ -1,5 +1,4 @@
--- Same predicates and hidden selectivities as q01 ('rare' ~0.1% is the
--- selective one, the rest 75-90%), but with 'rare' written first. cf. q01.
+-- q01 with the selective regexp written first.
 SELECT count(*) FROM t
 WHERE regexp_like(s, 'rare')
   AND regexp_like(s, 'aaa')
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql
index a583a498b2..a9d110c65a 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql
@@ -1,6 +1,4 @@
--- Hidden: `c0 < 90` matches ~90% (cheap integer compare); `regexp_like(s,
--- 'rare')` matches ~0.1% (scans the wide string). The cheaper predicate is the
--- less selective one.
+-- Cheap unselective compare (~90%) then expensive selective regexp (~0.1%). 
cf. q04.
 SELECT count(*) FROM t
 WHERE c0 < 90
   AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql
new file mode 100644
index 0000000000..2c5a253664
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql
@@ -0,0 +1,4 @@
+-- q03 the other way round, so the as-written order is already the best one.
+SELECT count(*) FROM t
+WHERE regexp_like(s, 'rare')
+  AND c0 < 90;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql
index b8cb61e85a..63e55ce473 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql
@@ -1,7 +1,5 @@
--- The non-obvious property: selectivity changes across the scan. Rows arrive 
in
--- `seq` order; `a_sel = 0` matches ~0.1% in the first 10% of rows and ~50%
--- after, `b_sel = 0` is the mirror -- so which predicate is more selective 
flips
--- partway through. cf. q81 (opposite order).
+-- Selectivity drifts across the scan: `a_sel = 0` matches ~0.1% over the 
first 2%
+-- of rows and ~50% after, and `b_sel = 0` is the mirror.
 SELECT count(*) FROM t
 WHERE a_sel = 0
   AND b_sel = 0;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql
deleted file mode 100644
index d65ef475cc..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql
+++ /dev/null
@@ -1,5 +0,0 @@
--- Same drifting predicates as q80 (a_sel/b_sel flip which is more selective
--- partway through the scan), opposite written order. cf. q80.
-SELECT count(*) FROM t
-WHERE b_sel = 0
-  AND a_sel = 0;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql
new file mode 100644
index 0000000000..57bb65d216
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql
@@ -0,0 +1,5 @@
+-- Late flip: one stream reads f=00..f=15 in order, so the drift lands halfway
+-- through the scan and a warm-up-and-freeze decision is wrong for half the 
rows.
+SELECT count(*) FROM t
+WHERE a_sel = 0
+  AND b_sel = 0;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql
new file mode 100644
index 0000000000..7ecc0fb1c5
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql
@@ -0,0 +1,5 @@
+-- Per-partition skew: one whole file per stream, so each stream sees a single 
fixed
+-- profile and one pooled decision is backwards for half of them.
+SELECT count(*) FROM t
+WHERE a_sel = 0
+  AND b_sel = 0;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql
index b217f56953..76f5a154da 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql
@@ -1,5 +1,4 @@
--- Hidden: four integer compares of equal cost, each ~50% selective. Nothing is
--- selective and the costs are equal, so the predicates are interchangeable.
+-- Four equally cheap compares, each ~50%: nothing to reorder.
 SELECT count(*) FROM t
 WHERE c0 < 50
   AND c1 < 50
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql
index 7029a3d9f8..43225f2e48 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql
@@ -1,6 +1,4 @@
--- Hidden: four regexp scans of about equal cost, all unselective ('aaa' ~90%,
--- 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%). Like q60 the predicates are
--- interchangeable, but here each one is expensive.
+-- Four equally expensive regexps, all unselective (75-90%): nothing to 
reorder.
 SELECT count(*) FROM t
 WHERE regexp_like(s, 'aaa')
   AND regexp_like(s, 'bbb')
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql
new file mode 100644
index 0000000000..ce4e40d9ac
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql
@@ -0,0 +1,6 @@
+-- The selective conjunct (~4%) is nullable, so AND pre-selection is disabled 
and
+-- writing it first gates nothing. cf. q91.
+SELECT count(*) FROM t
+WHERE c_sel < 5
+  AND c0 < 90
+  AND c1 < 90;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql
new file mode 100644
index 0000000000..abca8bb1f9
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql
@@ -0,0 +1,5 @@
+-- q90 with the nullable selective conjunct written last, which is the same 
work.
+SELECT count(*) FROM t
+WHERE c0 < 90
+  AND c1 < 90
+  AND c_sel < 5;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql
deleted file mode 100644
index 03a0f1c0db..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql
+++ /dev/null
@@ -1,6 +0,0 @@
--- Same predicates as costsel/q03 (`c0 < 90` ~90% cheap, `regexp_like(s, 
'rare')`
--- ~0.1% expensive). q50..q53 sweep table size; here PRED_ROWS=5_000, roughly a
--- single batch.
-SELECT count(*) FROM t
-WHERE c0 < 90
-  AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql
deleted file mode 100644
index 28174a5df4..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- q50 at PRED_ROWS=100_000 (~12 batches). See q50.
-SELECT count(*) FROM t
-WHERE c0 < 90
-  AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql
deleted file mode 100644
index 74938c4634..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- q50 at PRED_ROWS=5_000_000 (~610 batches). See q50.
-SELECT count(*) FROM t
-WHERE c0 < 90
-  AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql
deleted file mode 100644
index 8edb4d4a05..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- q50 at PRED_ROWS=50_000_000 (~6100 batches); builds a ~9 GB table. See q50.
-SELECT count(*) FROM t
-WHERE c0 < 90
-  AND regexp_like(s, 'rare');
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql
index 3638f757a7..b834a4bd7b 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql
@@ -1,6 +1,5 @@
--- Hidden: two equally cheap integer compares of unequal selectivity -- `c4 < 
95`
--- matches ~95%, `c0 < 5` matches ~5%. Less selective one written first.
--- cf. q21 (opposite order).
+-- Two equally cheap compares of unequal selectivity (~95%, ~5%), less 
selective
+-- first. cf. q21.
 SELECT count(*) FROM t
 WHERE c4 < 95
   AND c0 < 5;
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql
index 5181faf387..1f8531a510 100644
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql
+++ b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql
@@ -1,5 +1,4 @@
--- Same two equally-cheap compares as q20 (`c4 < 95` ~95%, `c0 < 5` ~5%),
--- opposite written order. cf. q20.
+-- q20 with the selective compare written first.
 SELECT count(*) FROM t
 WHERE c0 < 5
   AND c4 < 95;
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql
deleted file mode 100644
index 1b3df3e937..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql
+++ /dev/null
@@ -1,9 +0,0 @@
--- Same predicate set and hidden selectivities as costsel/q01 ('rare' ~0.1%, 
the
--- rest 75-90%); only the string-column width differs across q40/q41/q42. 
Narrow:
--- PRED_FILL=2, ~12 chars/row.
-SELECT count(*) FROM t
-WHERE regexp_like(s, 'aaa')
-  AND regexp_like(s, 'bbb')
-  AND regexp_like(s, 'ccc')
-  AND regexp_like(s, 'ddd')
-  AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql
deleted file mode 100644
index a03b576d9c..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql
+++ /dev/null
@@ -1,7 +0,0 @@
--- q40 with wide strings: PRED_FILL=30, ~186 chars/row. See q40.
-SELECT count(*) FROM t
-WHERE regexp_like(s, 'aaa')
-  AND regexp_like(s, 'bbb')
-  AND regexp_like(s, 'ccc')
-  AND regexp_like(s, 'ddd')
-  AND regexp_like(s, 'rare');
diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql 
b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql
deleted file mode 100644
index cb55d828e3..0000000000
--- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql
+++ /dev/null
@@ -1,7 +0,0 @@
--- q40 with extra-wide strings: PRED_FILL=170, ~1KB/row. See q40.
-SELECT count(*) FROM t
-WHERE regexp_like(s, 'aaa')
-  AND regexp_like(s, 'bbb')
-  AND regexp_like(s, 'ccc')
-  AND regexp_like(s, 'ddd')
-  AND regexp_like(s, 'rare');
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv
new file mode 100644
index 0000000000..94f68af265
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv
@@ -0,0 +1,2 @@
+count(*)
+40000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv
new file mode 100644
index 0000000000..6291042419
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv
@@ -0,0 +1,2 @@
+count(*)
+10000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv
new file mode 100644
index 0000000000..e3795c1d30
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv
@@ -0,0 +1,2 @@
+count(*)
+200000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv
new file mode 100644
index 0000000000..4303b90458
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv
@@ -0,0 +1,2 @@
+count(*)
+0
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv
new file mode 100644
index 0000000000..1cabb81336
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv
@@ -0,0 +1,2 @@
+count(*)
+90000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv
new file mode 100644
index 0000000000..6291042419
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv
@@ -0,0 +1,2 @@
+count(*)
+10000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv
new file mode 100644
index 0000000000..6291042419
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv
@@ -0,0 +1,2 @@
+count(*)
+10000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv
new file mode 100644
index 0000000000..5eb0b761fa
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv
@@ -0,0 +1,2 @@
+count(*)
+508
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv
new file mode 100644
index 0000000000..5eb0b761fa
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv
@@ -0,0 +1,2 @@
+count(*)
+508
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv
new file mode 100644
index 0000000000..1c82d80b6f
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv
@@ -0,0 +1,2 @@
+count(*)
+893
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv
new file mode 100644
index 0000000000..1c82d80b6f
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv
@@ -0,0 +1,2 @@
+count(*)
+893
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv
new file mode 100644
index 0000000000..c543899cb1
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv
@@ -0,0 +1,2 @@
+count(*)
+1000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv
new file mode 100644
index 0000000000..571ea61b27
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv
@@ -0,0 +1,2 @@
+count(*)
+992
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv
new file mode 100644
index 0000000000..571ea61b27
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv
@@ -0,0 +1,2 @@
+count(*)
+992
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv
new file mode 100644
index 0000000000..a1c39233b5
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv
@@ -0,0 +1,2 @@
+count(*)
+150000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv
new file mode 100644
index 0000000000..5d7624926c
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv
@@ -0,0 +1,2 @@
+count(*)
+514286
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv
new file mode 100644
index 0000000000..ce62ca3d04
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv
@@ -0,0 +1,2 @@
+count(*)
+20000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv
new file mode 100644
index 0000000000..ce62ca3d04
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv
@@ -0,0 +1,2 @@
+count(*)
+20000
diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv
new file mode 100644
index 0000000000..8cedd33498
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv
@@ -0,0 +1,2 @@
+count(*)
+5
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv
new file mode 100644
index 0000000000..5066201ce8
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv
@@ -0,0 +1,2 @@
+count(*)
+90
diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv
new file mode 100644
index 0000000000..41c4f54ec1
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv
@@ -0,0 +1,2 @@
+count(*)
+4461
diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv
new file mode 100644
index 0000000000..defd47ee70
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv
@@ -0,0 +1,2 @@
+count(*)
+44600
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv
 
b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv
new file mode 100644
index 0000000000..23d7344bc8
--- /dev/null
+++ 
b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv
@@ -0,0 +1,2 @@
+count(*)
+50000
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv
new file mode 100644
index 0000000000..5eb0b761fa
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv
@@ -0,0 +1,2 @@
+count(*)
+508
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv
new file mode 100644
index 0000000000..5eb0b761fa
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv
@@ -0,0 +1,2 @@
+count(*)
+508
diff --git 
a/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv 
b/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv
new file mode 100644
index 0000000000..5eb0b761fa
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv
@@ -0,0 +1,2 @@
+count(*)
+508
diff --git a/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore 
b/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore
new file mode 100644
index 0000000000..4bed5da93f
--- /dev/null
+++ b/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore
@@ -0,0 +1 @@
+*.parquet


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to