chore: upgrade DataFusion to 54#7793
Conversation
geodatafusion 0.5.0 supports DataFusion 54, which was the last remaining blocker. This bumps DataFusion 53 -> 54, and transitively geodatafusion 0.4 -> 0.5, sqlparser 0.61 -> 0.62, and substrait 0.62 -> 0.63. Arrow stays at 58. Migration for DataFusion 54 breaking changes: - `as_any` was removed from the `ExecutionPlan`, `PhysicalExpr`, `ScalarUDFImpl`, `TableProvider`, `SchemaProvider`, `CatalogProvider`, and `CatalogProviderList` traits. Drop the impls and downcast via the inherent `dyn Trait::downcast_ref`/`is` instead of `.as_any().downcast_ref()`. - `ExecutionPlan::partition_statistics` now returns `Arc<Statistics>`. - `Expr::Cast`/`TryCast` hold a `FieldRef` instead of a `DataType`. - `AnalyzeExec::new` gained a `metric_categories` argument and `MetricType::SUMMARY` was renamed to `MetricType::Summary`. - sqlparser 0.62 wraps the LIKE escape character in `ValueWithSpan`. - substrait 0.63 adds `RexType::Lambda`/`LambdaInvocation` and replaces extension URIs with URNs. - Migrate the deprecated `SimplifyContext::default()` builder to `SimplifyContext::builder().build()`. `create_aggregate_expr_and_maybe_filter` is kept under `#[allow(deprecated)]`; migrating to `LoweredAggregateBuilder` is left as follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR upgrades DataFusion integrations from 53 to 54, updates planner and Substrait APIs, migrates execution-plan statistics and downcasting interfaces, and adjusts scalar expression handling, tests, and language bindings. ChangesDataFusion 54 compatibility
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
The substrait 0.63 bump removed the extension-URI API (`SimpleExtensionUri`, `Plan.extension_uris`, `ExtensionFunction.extension_uri_reference`) in favor of URNs. This test file was missed in the DF54 migration because it only compiles under the `substrait` feature, which the default workspace clippy/build does not enable. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…rder-independent DataFusion 54's optimizer reorders the pushed-down HashJoinExec projection in the full-schema delete-by-source case, moving `__merge_source_sentinel` ahead of `_rowid` to match the consuming ProjectionExec's column order. The plan is still correct (indices line up), but the positional `projection=[_rowid` assertion no longer held. Assert instead that the join keeps a pushed-down projection containing `_rowid`, which is what the test actually cares about. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
pylance now exports a datafusion-ffi 54 TableProvider, but the PyPI `datafusion` package was pinned to 53. The mismatched FFI struct layout segfaulted `test_table_provider.py::test_table_loading` when datafusion 53 consumed the v54 provider via `register_table`. Bump the dependency (and the test-group pin) to 54 so both sides share the same FFI ABI. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@rust/lance-datafusion/src/planner.rs`:
- Around line 797-800: Update the escape handling shared by SQLExpr::ILike and
SQLExpr::Like so a SingleQuotedString is accepted only when it contains exactly
one character; reject empty or multi-character strings with an input error
instead of using char.chars().next() and silently altering the predicate.
In `@rust/lance-datafusion/src/substrait.rs`:
- Around line 167-169: Ensure Lambda and LambdaInvocation expressions are
rejected on every expression-parse path, not only when schema references are
remapped. Update the parsing flow around remap_expr_references() to traverse and
validate expressions unconditionally while preserving remapping for pruned
schemas, and add a regression test using an unpruned schema that verifies the
invalid-input error.
In `@rust/lance/src/io/exec/count_from_mask.rs`:
- Around line 512-516: Move the create_aggregate_expr_and_maybe_filter import
out of count_star_expr and into the mod tests import block, keeping the
deprecation allowance applied to both the import and its use in the function.
In `@rust/lance/src/io/exec/filtered_read.rs`:
- Around line 2057-2060: Replace the unconditional Statistics clones at both
mutation sites with Arc::unwrap_or_clone so unique Arc allocations are reused:
update the partition_statistics handling in
rust/lance/src/io/exec/filtered_read.rs (lines 2057-2060) and the corresponding
.as_ref().clone() in rust/lance/src/io/exec/rowids.rs (line 291).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 9dceccfe-8778-492d-93b1-90a1d2522a23
⛔ Files ignored due to path filters (4)
Cargo.lockis excluded by!**/*.lockjava/lance-jni/Cargo.lockis excluded by!**/*.lockpython/Cargo.lockis excluded by!**/*.lockpython/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (46)
Cargo.tomljava/lance-jni/Cargo.tomlpython/Cargo.tomlpython/pyproject.tomlrust/lance-datafusion/src/exec.rsrust/lance-datafusion/src/planner.rsrust/lance-datafusion/src/substrait.rsrust/lance-index/src/scalar/btree.rsrust/lance-index/src/scalar/expression.rsrust/lance-index/src/scalar/rtree/sort/hilbert_sort.rsrust/lance-namespace-datafusion/src/catalog.rsrust/lance-namespace-datafusion/src/schema.rsrust/lance/src/datafusion/dataframe.rsrust/lance/src/datafusion/logical_plan.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/brute_force_vector.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/btree.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/dedup_scan.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/fts.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/scan.rsrust/lance/src/dataset/mem_wal/memtable/scanner/exec/vector.rsrust/lance/src/dataset/mem_wal/scanner/exec/bloom_guard.rsrust/lance/src/dataset/mem_wal/scanner/exec/coalesce_first.rsrust/lance/src/dataset/mem_wal/scanner/exec/generation_tag.rsrust/lance/src/dataset/mem_wal/scanner/exec/pk_block_filter.rsrust/lance/src/dataset/scanner.rsrust/lance/src/dataset/schema_evolution/optimize.rsrust/lance/src/dataset/tests/dataset_aggregate.rsrust/lance/src/dataset/udtf.rsrust/lance/src/dataset/write/merge_insert.rsrust/lance/src/dataset/write/merge_insert/exec/delete.rsrust/lance/src/dataset/write/merge_insert/exec/write.rsrust/lance/src/io/exec/count_from_mask.rsrust/lance/src/io/exec/count_pushdown.rsrust/lance/src/io/exec/filter.rsrust/lance/src/io/exec/filtered_read.rsrust/lance/src/io/exec/fts.rsrust/lance/src/io/exec/knn.rsrust/lance/src/io/exec/optimizer.rsrust/lance/src/io/exec/pushdown_scan.rsrust/lance/src/io/exec/rowids.rsrust/lance/src/io/exec/scalar_index.rsrust/lance/src/io/exec/scan.rsrust/lance/src/io/exec/take.rsrust/lance/src/io/exec/testing.rsrust/lance/src/io/exec/utils.rsrust/lance/tests/count_pushdown/mod.rs
💤 Files with no reviewable changes (12)
- rust/lance/src/dataset/udtf.rs
- rust/lance/src/dataset/mem_wal/scanner/exec/coalesce_first.rs
- rust/lance/src/io/exec/testing.rs
- rust/lance-index/src/scalar/rtree/sort/hilbert_sort.rs
- rust/lance/src/dataset/write/merge_insert/exec/delete.rs
- rust/lance/src/dataset/write/merge_insert/exec/write.rs
- rust/lance-namespace-datafusion/src/catalog.rs
- rust/lance/src/dataset/mem_wal/scanner/exec/pk_block_filter.rs
- rust/lance/src/dataset/mem_wal/scanner/exec/bloom_guard.rs
- rust/lance/src/dataset/mem_wal/scanner/exec/generation_tag.rs
- rust/lance-namespace-datafusion/src/schema.rs
- rust/lance/src/io/exec/utils.rs
| Some(ValueWithSpan { | ||
| value: Value::SingleQuotedString(char), | ||
| .. | ||
| }) => char.chars().next(), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the file and inspect the relevant regions with line numbers.
wc -l rust/lance-datafusion/src/planner.rs
sed -n '760,840p' rust/lance-datafusion/src/planner.rs
sed -n '800,840p' rust/lance-datafusion/src/planner.rs
sed -n '1200,1245p' rust/lance-datafusion/src/planner.rs
# Search for LIKE/escape related helpers and tests.
rg -n "ESCAPE|escape|Like" rust/lance-datafusion/src/planner.rs rust/lance-datafusion -g '!**/target/**'Repository: lance-format/lance
Length of output: 10362
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l rust/lance-datafusion/src/planner.rs
sed -n '760,840p' rust/lance-datafusion/src/planner.rs
sed -n '1200,1245p' rust/lance-datafusion/src/planner.rs
rg -n "ESCAPE|escape|Like" rust/lance-datafusion/src/planner.rs rust/lance-datafusion -g '!**/target/**'Repository: lance-format/lance
Length of output: 8744
🏁 Script executed:
echo testRepository: lance-format/lance
Length of output: 161
Reject invalid LIKE escape strings. Both SQLExpr::ILike and SQLExpr::Like should require exactly one escape character; char.chars().next() turns '' into None and truncates 'ab' to 'a', which changes predicate behavior instead of returning an input error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rust/lance-datafusion/src/planner.rs` around lines 797 - 800, Update the
escape handling shared by SQLExpr::ILike and SQLExpr::Like so a
SingleQuotedString is accepted only when it contains exactly one character;
reject empty or multi-character strings with an input error instead of using
char.chars().next() and silently altering the predicate.
Source: Coding guidelines
| RexType::Lambda(_) | RexType::LambdaInvocation(_) => Err(Error::invalid_input( | ||
| "Lambda expressions not allowed in filter expression", | ||
| )), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="rust/lance-datafusion/src/substrait.rs"
# Map the file structure first.
ast-grep outline "$FILE" --view expanded
echo
echo "----- relevant slices -----"
sed -n '120,240p' "$FILE"
echo
sed -n '520,620p' "$FILE"
echo
sed -n '840,940p' "$FILE"Repository: lance-format/lance
Length of output: 15547
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for lambda-related validation and remapping call sites.
rg -n "Lambda|LambdaInvocation|remap_expr_references|parse_substrait|extension-type|schema length|filter" rust/lance-datafusion/src/substrait.rsRepository: lance-format/lance
Length of output: 4326
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="rust/lance-datafusion/src/substrait.rs"
# Print only the parse_substrait function and the remap_expr_references helper.
python3 - <<'PY'
from pathlib import Path
path = Path("rust/lance-datafusion/src/substrait.rs")
lines = path.read_text().splitlines()
for start, end, label in [(1, 260, "top"), (520, 640, "middle"), (840, 960, "bottom")]:
print(f"\n===== {label} {start}-{end} =====")
for i in range(start, min(end, len(lines)) + 1):
print(f"{i:4d}: {lines[i-1]}")
PYRepository: lance-format/lance
Length of output: 23773
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '267,330p' rust/lance-datafusion/src/substrait.rsRepository: lance-format/lance
Length of output: 2559
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path("rust/lance-datafusion/src/substrait.rs")
for i, line in enumerate(path.read_text().splitlines(), start=1):
if 267 <= i <= 330:
print(f"{i:4d}: {line}")
PYRepository: lance-format/lance
Length of output: 2943
Reject lambda expressions on every parse path. Lambda and LambdaInvocation are only rejected inside remap_expr_references(), so plans that don’t drop any schema fields skip this check and can still reach the consumer. Split validation from remapping, or run the traversal unconditionally, and add a regression test with an unpruned schema.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rust/lance-datafusion/src/substrait.rs` around lines 167 - 169, Ensure Lambda
and LambdaInvocation expressions are rejected on every expression-parse path,
not only when schema references are remapped. Update the parsing flow around
remap_expr_references() to traverse and validate expressions unconditionally
while preserving remapping for pruned schemas, and add a regression test using
an unpruned schema that verifies the invalid-input error.
Source: Coding guidelines
| let mut df_stats = df_filter_exec | ||
| .partition_statistics(partition)? | ||
| .as_ref() | ||
| .clone(); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
Avoid unconditional deep copies of Arc-wrapped Statistics.
Use Arc::unwrap_or_clone at both mutation sites so unique allocations are reused. This aligns with the DataFusion 54 migration guidance. (datafusion.apache.org)
rust/lance/src/io/exec/filtered_read.rs#L2057-L2060: replace.as_ref().clone()withArc::unwrap_or_clone(...).rust/lance/src/io/exec/rowids.rs#L291-L291: replace.as_ref().clone()withArc::unwrap_or_clone(...).
📍 Affects 2 files
rust/lance/src/io/exec/filtered_read.rs#L2057-L2060(this comment)rust/lance/src/io/exec/rowids.rs#L291-L291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rust/lance/src/io/exec/filtered_read.rs` around lines 2057 - 2060, Replace
the unconditional Statistics clones at both mutation sites with
Arc::unwrap_or_clone so unique Arc allocations are reused: update the
partition_statistics handling in rust/lance/src/io/exec/filtered_read.rs (lines
2057-2060) and the corresponding .as_ref().clone() in
rust/lance/src/io/exec/rowids.rs (line 291).
Source: MCP tools
- Reuse unique Arc<Statistics> allocations via Arc::unwrap_or_clone in the partition_statistics overrides (filtered_read, rowids) instead of an unconditional deep copy, per the DataFusion 54 upgrade guidance. - Move the deprecated create_aggregate_expr_and_maybe_filter import out of the count_star_expr test helper into the test module's import block. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
geodatafusion 0.5.0supports DataFusion 54, which was the last remaining blocker. This bumps DataFusion 53 → 54, and transitivelygeodatafusion0.4 → 0.5,sqlparser0.61 → 0.62, andsubstrait0.62 → 0.63. Arrow stays at 58.Migration for DataFusion 54 breaking changes
as_anywas removed from theExecutionPlan,PhysicalExpr,ScalarUDFImpl,TableProvider,SchemaProvider,CatalogProvider, andCatalogProviderListtraits. The impls are dropped and downcasting now uses the inherentdyn Trait::downcast_ref/is(e.g.plan.downcast_ref::<T>()instead ofplan.as_any().downcast_ref::<T>()).ExecutionPlan::partition_statisticsnow returnsArc<Statistics>.Expr::Cast/TryCasthold aFieldRefinstead of aDataType.AnalyzeExec::newgained ametric_categoriesargument andMetricType::SUMMARYwas renamed toMetricType::Summary.ValueWithSpan.RexType::Lambda/LambdaInvocationvariants and replaces extension URIs with URNs.SimplifyContext::default()builder toSimplifyContext::builder().build().create_aggregate_expr_and_maybe_filteris retained under#[allow(deprecated)]; migrating toLoweredAggregateBuilderis left as follow-up.Verification
cargo clippy --all --tests --benches -- -D warningsclean, plus-p lance-datafusion --features substrait.lance-datafusionsubstrait tests pass (exercising the URN andRexTypechanges).pylance) and Java (lance-jni) bindings compile; all three lockfiles refreshed.🤖 Generated with Claude Code
Summary by CodeRabbit