Repository navigation
Push down comparisons through REE-to-dictionary casts - #10637
Conversation
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>
Merging this PR will regress 9 benchmarks
|
| 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
Footnotes
-
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. ↩
-
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. ↩
Summary
For
ree_dict = 'x'on aRunEndEncoded<_, Dictionary<_, Utf8>>column, DataFusion's comparison coercion producesCAST(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 VortexUtf8, so Vortex has nothing to cast.Changes
supported_castbecomesclassify_cast, which either drops a cast, keeps it as a Vortexcast, or leaves it in DataFusion:Drop (the converted child is returned unchanged) when the Arrow cast cannot fail and the Vortex dtype is unchanged:
REE<Int16 | Int32, Dict<K, V>>→Dict<K, V>.Arrow expands runs with
takeonInt32indices, andtakeon a dictionary only gathers keys. A dropped cast no longer blocks pushdown underAND/ORor inINlists.Keep numeric widening, as before.
Leave in DataFusion everything else, including:
Int64run ends (Arrow's indices may wrap);REE<_, V>→Vwhen expanding values can fail (for example,Utf8/Binaryoffset overflow);Dict<K, V>→V(out of scope; see below);V→Dict<K, V>;LargeUtf8→Utf8.Tests:
=,ORandIN, asserting nocastnode remains..vortexscan vs.MemTabletest that also asserts the filter is accepted by the scan when pushdown is enabled.Follow-up scope
Dict<K, V>→Vis deliberately out of scope for this PR. Arrow decodes a dictionary by gathering one value per key. For offset-based values such asUtf8orBinary, 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.