Skip to content

Push down comparisons through REE-to-dictionary casts - #10637

Merged
robert3005 merged 5 commits into
vortex-data:developfrom
yiquanzhou:fix/ree-dict-cast-pushdown
Oct 20, 2026
Merged

robert3005 merged 5 commits into
vortex-data:developfrom
yiquanzhou:fix/ree-dict-cast-pushdown

Conversation

@yiquanzhou

@yiquanzhou yiquanzhou commented Oct 20, 2026 •

Copy link
Copy Markdown

Summary

For ree_dict = 'x' on a RunEndEncoded<_, Dictionary<_, Utf8>> column, DataFusion's comparison coercion produces CAST(ree_dict AS Dictionary(UInt32, Utf8)) = 'x'. Since #9785 the converter rejects that cast, so the filter runs in DataFusion instead of the scan. Results stay correct, but pushdown is lost. Both Arrow types map to Vortex Utf8, so Vortex has nothing to cast.

Changes

supported_cast becomes classify_cast, which either drops a cast, keeps it as a Vortex cast, or leaves it in DataFusion:

  • Drop (the converted child is returned unchanged) when the Arrow cast cannot fail and the Vortex dtype is unchanged:

    • identity casts;
    • REE<Int16 | Int32, Dict<K, V>> → Dict<K, V>.

    Arrow expands runs with take on Int32 indices, and take on a dictionary only gathers keys. A dropped cast no longer blocks pushdown under AND/OR or in IN lists.

  • Keep numeric widening, as before.

  • Leave in DataFusion everything else, including:

    • Int64 run ends (Arrow's indices may wrap);
    • REE<_, V> → V when expanding values can fail (for example, Utf8/Binary offset overflow);
    • Dict<K, V> → V (out of scope; see below);
    • dictionary key changes;
    • V → Dict<K, V>;
    • LargeUtf8 → Utf8.

Tests:

  • The issue's repro (coerce, simplify, plan) for =, OR and IN, asserting no cast node remains.
  • Accepted and rejected casts.
  • Vortex vs. DataFusion evaluation on an REE batch with nulls and unreferenced dictionary values.
  • A .vortex scan vs. MemTable test that also asserts the filter is accepted by the scan when pushdown is enabled.

Follow-up scope

Dict<K, V> → V is deliberately out of scope for this PR. Arrow decodes a dictionary by gathering one value per key. For offset-based values such as Utf8 or Binary, the decoded output can exceed the offset limit and the cast can fail. A follow-up can allow specific value types only after checking that the Arrow cast cannot fail, preserves values and nulls, and leaves the expected Vortex dtype. For now, all dictionary-to-value casts stay in DataFusion.

Known limitation: an unsimplified predicate still casts the literal (Utf8 → Dict), which remains unsupported.

AI assistance

Disclosed per the AI policy: the investigation, code and tests were drafted with Claude (Claude Code) and reviewed by me.

DataFusion wraps REE<Dict<Utf8>> columns in CAST(... AS Dictionary(..., Utf8))
before comparing them. Since vortex-data#9785 the converter rejected that cast, so the
predicate stayed in DataFusion. Drop casts that cannot fail in Arrow and map
to the same Vortex dtype (identity, and REE with Int16/Int32 run ends over a
dictionary to that dictionary) instead of emitting a fallible Vortex cast.

Fixes vortex-data#10634

Signed-off-by: Yiquan Zhou <yiquan.zhou@dash0.com>
Signed-off-by: Yiquan Zhou <yiquan.zhou@dash0.com>
…t-pushdown

* upstream/develop:
  Prune in_list expressions (vortex-data#10617)
  Fix semantic conflict in c++ tests (vortex-data#10638)

Signed-off-by: Yiquan Zhou <yiquan.zhou@dash0.com>

# Conflicts:
#	vortex-datafusion/src/convert/exprs/tests.rs
Return Conversion<CastPushdown> so unsupported casts use the existing
Unconverted::Unsupported, express the rules as one match on the source and
target types, and match on the decision at the call site.

Signed-off-by: Yiquan Zhou <yiquan.zhou@dash0.com>
Signed-off-by: Yiquan Zhou <yiquan.zhou@dash0.com>
@robert3005 robert3005 added the changelog/fix A bug fix label Oct 20, 2026
@codspeed

codspeed Bot commented Oct 20, 2026

Copy link
Copy Markdown

Merging this PR will regress 9 benchmarks

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ 1 benchmark measured no execution time

Nothing ran under measurement, usually because the compiler removed the code under test. This result is not comparable, so it counts as unchanged.

Preventing compiler optimizations

⚠️ 16 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

ℹ️ 40 benchmarks have no flame graph

The flamegraph could not be generated because we failed to resolve the symbols in the profiling trace. This happens when we fail to harvest information about language frames.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 17 improved benchmarks
❌ 9 regressed benchmarks
✅ 2382 untouched benchmarks
⏩ 12 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ WallTime mul_u64_nonnull_neon 20.5 µs 39 µs -47.49%
❌ WallTime filter_neon[8] 909 ns 1,549 ns -41.32%
❌ WallTime filter_neon[64] 1.8 µs 2.9 µs -39.45%
❌ WallTime threshold_neon[8] 1.4 µs 2.4 µs -39.25%
❌ WallTime filter_neon[80] 2.5 µs 3.5 µs -28.41%
❌ Simulation density_sweep_dense_runs[0.001] 44.5 µs 61.4 µs -27.55%
❌ WallTime threshold_neon[64] 2.7 µs 3.6 µs -25.75%
❌ WallTime threshold_neon[80] 3.5 µs 4.5 µs -20.79%
❌ WallTime starts_with_avx512[64, VarBin] 53.3 µs 59.3 µs -10.17%
⚡ Simulation column_x_constant 580.9 µs 153.5 µs ×3.8
⚡ Simulation column_x_column 168.6 µs 107.9 µs +56.26%
⚡ Simulation nullable_columns 183.2 µs 124.7 µs +46.99%
⚡ ℹ️ Simulation test_repeated_scan_scalar_at[int] 354.3 µs 245.4 µs +44.39%
⚡ ⚠️ Simulation random_i128[0.01] 6.4 µs 5.4 µs +16.79%
⚡ ℹ️ Simulation test_scan_filter_to_arrow_array[int_float] 4.1 ms 3.5 ms +15.59%
⚡ ℹ️ Simulation test_scan_scalar_at[int_float] 1,079.2 µs 956.9 µs +12.78%
⚡ WallTime bitpack_blocked_compress_avx2 7.7 µs 6.8 µs +12.6%
⚡ ℹ️ Simulation test_repeated_scan_filter[int] 2.8 ms 2.5 ms +12.46%
⚡ Simulation eight_points 642.6 µs 571.7 µs +12.4%
⚡ ℹ️ Simulation test_repeated_scan_filter[int_float] 3.8 ms 3.4 ms +11.83%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing yiquanzhou:fix/ree-dict-cast-pushdown (b6191f9) with develop (f990a5d)2

Open in CodSpeed

Footnotes

  1. 12 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on develop (c48457b) during the generation of this report, so f990a5d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@robert3005
robert3005 merged commit c6da04b into vortex-data:develop Oct 20, 2026
113 of 115 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants