Skip to content

feat: serialize ASOF join plans - #23832

Open
Xuanwo wants to merge 14 commits into
apache:mainfrom
Xuanwo:xuanwo/asof-proto
Open

feat: serialize ASOF join plans#23832
Xuanwo wants to merge 14 commits into
apache:mainfrom
Xuanwo:xuanwo/asof-proto

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Jul 23, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

This is the serialization layer of the ASOF JOIN stack. It gives logical and
physical ASOF plans explicit protobuf representations without coupling wire
format review to SQL or DataFrame APIs.

#23829 and its prerequisites are merged. After restacking onto the current
main, this PR now contains only the serialization layer.

What changes are included in this PR?

  • Add protobuf messages and enum values for logical and physical ASOF joins.
  • Encode and decode equality keys, ordered match expressions, match direction,
    join constraint, and right output indices.
  • Implement physical serialization through ExecutionPlan::try_to_proto and
    AsOfJoinExec::try_from_proto, following the current self-serializing
    execution-plan pattern.
  • Regenerate prost and pbjson sources with the repository generator. The
    physical-plan oneof uses the next append-only tag after
    PiecewiseMergeJoinExec.
  • Add logical round trips for all four match directions and a physical
    AsOfJoinExec round trip.

Are these changes tested?

Yes:

  • ./datafusion/proto-models/regen.sh
  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • ./dev/rust_lint.sh
  • cargo test -p datafusion-proto roundtrip_asof_join --all-features
  • The extended workspace test command from the contributor guide

Are there any user-facing changes?

Logical and physical ASOF join plans can be serialized through
datafusion-proto. The additions use new messages and append-only oneof/enum
tags, so existing wire tags are not reused. Generated public Rust enums gain new
variants, however, so downstream exhaustive matches must add arms; this is a
Rust source-compatibility break even though the wire additions are compatible.

This PR can now be reviewed independently. It does not depend on the optional
floating-point follow-up #24375.

@github-actions github-actions Bot added sql SQL Planner logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate substrait Changes to the substrait crate proto Related to proto crate physical-plan Changes to the physical-plan crate labels Jul 23, 2026
@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 23, 2026
@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 23.25142% with 406 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.86%. Comparing base (bb21f51) to head (f6b4d55).

Files with missing lines Patch % Lines
datafusion/proto-models/src/generated/pbjson.rs 0.00% 341 Missing ⚠️
datafusion/proto/src/logical_plan/mod.rs 55.73% 17 Missing and 10 partials ⚠️
datafusion/physical-plan/src/joins/asof_join.rs 80.73% 10 Missing and 11 partials ⚠️
datafusion/proto-models/src/generated/prost.rs 0.00% 17 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23832      +/-   ##
==========================================
- Coverage   81.93%   81.86%   -0.08%     
==========================================
  Files        1133     1133              
  Lines      423529   424058     +529     
  Branches   423529   424058     +529     
==========================================
+ Hits       347032   347148     +116     
- Misses      55907    56296     +389     
- Partials    20590    20614      +24     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added the physical-expr Changes to the physical-expr crates label Aug 14, 2026
@Xuanwo
Xuanwo force-pushed the xuanwo/asof-proto branch from 19940d8 to 6ae0de3 Compare August 20, 2026 17:56
@Xuanwo
Xuanwo force-pushed the xuanwo/asof-proto branch from 6ae0de3 to 54f3afb Compare August 28, 2026 05:40
@github-actions github-actions Bot removed physical-expr Changes to the physical-expr crates auto detected api change Auto detected API change labels Aug 28, 2026
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion-physical-plan v55.0.0 (current)
       Built [  41.757s] (current)
     Parsing datafusion-physical-plan v55.0.0 (current)
      Parsed [   0.159s] (current)
    Building datafusion-physical-plan v55.0.0 (baseline)
       Built [  41.328s] (baseline)
     Parsing datafusion-physical-plan v55.0.0 (baseline)
      Parsed [   0.161s] (baseline)
    Checking datafusion-physical-plan v55.0.0 -> v55.0.0 (no change; assume patch)
     Checked [   0.681s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  85.458s] datafusion-physical-plan
    Building datafusion-proto v55.0.0 (current)
       Built [  58.793s] (current)
     Parsing datafusion-proto v55.0.0 (current)
      Parsed [   0.018s] (current)
    Building datafusion-proto v55.0.0 (baseline)
       Built [  58.847s] (baseline)
     Parsing datafusion-proto v55.0.0 (baseline)
      Parsed [   0.019s] (baseline)
    Checking datafusion-proto v55.0.0 -> v55.0.0 (no change; assume patch)
     Checked [   0.108s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 118.992s] datafusion-proto
    Building datafusion-proto-models v55.0.0 (current)
       Built [  26.790s] (current)
     Parsing datafusion-proto-models v55.0.0 (current)
      Parsed [   0.133s] (current)
    Building datafusion-proto-models v55.0.0 (baseline)
       Built [  27.086s] (baseline)
     Parsing datafusion-proto-models v55.0.0 (baseline)
      Parsed [   0.133s] (baseline)
    Checking datafusion-proto-models v55.0.0 -> v55.0.0 (no change; assume patch)
     Checked [   1.827s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/enum_variant_added.ron

Failed in:
  variant PhysicalPlanType:AsOfJoin in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1461
  variant PhysicalPlanType:AsOfJoin in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1461
  variant LogicalPlanType:AsOfJoin in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:83
  variant LogicalPlanType:AsOfJoin in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:83

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  57.041s] datafusion-proto-models

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Aug 28, 2026
@github-actions github-actions Bot removed sql SQL Planner logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate labels Sep 11, 2026
@github-actions github-actions Bot removed the substrait Changes to the substrait crate label Sep 11, 2026
@Xuanwo
Xuanwo marked this pull request as ready for review September 11, 2026 10:23
@Xuanwo

Xuanwo commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

@2010YOUY01 & @jayzhan211, let's go!

@jayzhan211 jayzhan211 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Xuanwo! One non-blocking suggestion:

The two new serialization hooks read their fields by direct access rather than
destructuring, which skips the guard every other self-serializing plan in
physical-plan uses. From nested_loop_join.rs:

Destructure exhaustively (no ..) so that a newly added proto field
is a compile error here instead of being silently ignored.

That pattern is in cross_join.rs, hash_join/exec.rs, nested_loop_join.rs,
piecewise_merge_join/exec.rs, sort_merge_join/exec.rs and
symmetric_hash_join.rs — 49 sites in total, on both the encode and decode side.

Nothing is dropped today: I checked all 12 AsOfJoinExec fields and all 7
AsOfJoinExecNode fields against the diff and the coverage is complete. The
concern is the next change. The ASOF stack is still landing, and when a filter
or null_equality is added to AsOfJoinExec, this code keeps compiling and
starts emitting plans that decode into a different — but still executable —
join on the remote node. Silent field loss is the failure mode this convention
exists to prevent, so I'd like it applied here before merge.

Encode side (asof_join.rs:557)

     #[cfg(feature = "proto")]
     fn try_to_proto(
         &self,
         ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
     ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
         use datafusion_proto_models::protobuf;
 
+        // Destructure exhaustively (no `..`) so that a newly added field is a
+        // compile error here instead of being silently ignored.
+        let Self {
+            left,
+            right,
+            on,
+            match_condition,
+            projection,
+            // derived from the children's schemas by `try_new` on decode
+            join_schema: _,
+            // derived from the children's schemas by `try_new` on decode
+            column_indices: _,
+            // runtime metrics, not part of the plan
+            metrics: _,
+            // recomputed from `on` and `match_condition.op` by `try_new`
+            left_ordering: _,
+            // recomputed from `on` and `match_condition.op` by `try_new`
+            right_ordering: _,
+            // right input collected at execution time, not part of the plan
+            right_fut: _,
+            // recomputed by `try_new` on decode
+            cache: _,
+        } = self;
+
-        let left = ctx.encode_child(&self.left)?;
-        let right = ctx.encode_child(&self.right)?;
-        let on = self
-            .on
+        let left = ctx.encode_child(left)?;
+        let right = ctx.encode_child(right)?;
+        let on = on
             .iter()

…and the remaining self.match_condition / self.projection uses become
match_condition / projection.

Decode side (asof_join.rs:621), after the expect_plan_variant!

         let asof_join = crate::expect_plan_variant!(
             node,
             protobuf::physical_plan_node::PhysicalPlanType::AsOfJoin,
             "AsOfJoinExec",
         );
+        // Destructure exhaustively (no `..`) so that a newly added proto field
+        // is a compile error here instead of being silently ignored.
+        let protobuf::AsOfJoinExecNode {
+            left,
+            right,
+            on,
+            left_match_expr,
+            right_match_expr,
+            match_operator,
+            projection,
+        } = &**asof_join;
+
-        let left =
-            ctx.decode_required_child(asof_join.left.as_deref(), "AsOfJoinExec", "left")?;
+        let left = ctx.decode_required_child(left.as_deref(), "AsOfJoinExec", "left")?;

with the later asof_join.* accesses updated to the bound names.

@Xuanwo

Xuanwo commented Sep 12, 2026

Copy link
Copy Markdown
Member Author

Thank you @jayzhan211 for the review. I have updated this PR and fired #25214 🤓

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change physical-plan Changes to the physical-plan crate proto Related to proto crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants