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

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


The following commit(s) were added to refs/heads/main by this push:
     new f7aef23c6b refactor(proto): delegate deprecated ProjectionExec serde 
shims to new hooks (#23731)
f7aef23c6b is described below

commit f7aef23c6b7ff1edb06dfcb08b6afa5a77114000
Author: Adrian Garcia Badaracco <[email protected]>
AuthorDate: Thu Jul 23 11:10:17 2026 -0500

    refactor(proto): delegate deprecated ProjectionExec serde shims to new 
hooks (#23731)
    
    ## Which issue does this PR close?
    
    - Part of #23494.
    
    ## Rationale for this change
    
    #23495 landed the deprecated `ProjectionExec` compatibility shims
    (`try_from_projection_exec` / `try_into_projection_physical_plan`) with
    their
    original bodies fully duplicated in `datafusion-proto`, even though the
    same
    wire-format logic now lives in `ProjectionExec::try_to_proto` /
    `try_from_proto`
    (the #22419 hook pattern). This PR makes those deprecated shims
    **delegate** to
    the new hooks so the wire format is single-sourced in
    `datafusion-physical-plan`
    rather than duplicated. This matches the `FilterExec` delegate style in
    #23708.
    No wire-format change.
    
    ## What changes are included in this PR?
    
    - Replace the body of `try_from_projection_exec` with a thin shim that
    builds an
      `ExecutionPlanEncodeCtx` from the `codec` + `proto_converter` (via
    `ConverterPlanEncoder`) and delegates to `ProjectionExec::try_to_proto`.
    - Replace the body of `try_into_projection_physical_plan` with a thin
    shim that
    builds an `ExecutionPlanDecodeCtx` (via `ConverterPlanDecoder`) and
    delegates
    to `ProjectionExec::try_from_proto`; the now-unused `projection` param
    is
      renamed to `_projection`.
    - Drop the now-unused `ProjectionExpr` import.
    
    The `#[deprecated(...)]` attributes and signatures are otherwise
    unchanged, and
    the live dispatch arms and the `ProjectionExec` hook impls are
    untouched.
    
    ## Are these changes tested?
    
    Yes — the existing `roundtrip_physical_plan` projection/filter roundtrip
    tests
    exercise this path and pass (`roundtrip_projection_source`,
    `roundtrip_filter_with_fetch`, `roundtrip_filter_with_not_and_in_list`,
    and the
    full 203-test `roundtrip` suite). `cargo clippy -p datafusion-proto
    --all-features -- -D warnings` is clean.
    
    ## Are there any user-facing changes?
    
    No. The methods remain `#[deprecated]` with identical signatures and
    wire
    format; only their internals change.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01U9qX6kekbJGpmTSxvrU8e5
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 datafusion/proto/src/physical_plan/mod.rs          | 67 +++++++---------------
 .../proto/tests/cases/roundtrip_physical_plan.rs   | 58 +++++++++++++++++++
 2 files changed, 79 insertions(+), 46 deletions(-)

diff --git a/datafusion/proto/src/physical_plan/mod.rs 
b/datafusion/proto/src/physical_plan/mod.rs
index cea334e42a..518d7f01b1 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -90,7 +90,7 @@ use datafusion_physical_plan::limit::{GlobalLimitExec, 
LocalLimitExec};
 use datafusion_physical_plan::memory::LazyMemoryExec;
 use datafusion_physical_plan::metrics::MetricCategory;
 use datafusion_physical_plan::placeholder_row::PlaceholderRowExec;
-use datafusion_physical_plan::projection::{ProjectionExec, ProjectionExpr};
+use datafusion_physical_plan::projection::ProjectionExec;
 use datafusion_physical_plan::proto::{
     ExecutionPlanDecode, ExecutionPlanDecodeCtx, ExecutionPlanEncode,
     ExecutionPlanEncodeCtx,
@@ -1145,28 +1145,21 @@ pub trait PhysicalPlanNodeExt: Sized {
         ctx: &PhysicalPlanDecodeContext<'_>,
         proto_converter: &dyn PhysicalProtoConverterExtension,
     ) -> Result<Arc<dyn ExecutionPlan>> {
-        let input: Arc<dyn ExecutionPlan> =
-            into_physical_plan(&projection.input, ctx, proto_converter)?;
-        let exprs = projection
-            .expr
-            .iter()
-            .zip(projection.expr_name.iter())
-            .map(|(expr, name)| {
-                Ok((
-                    proto_converter.proto_to_physical_expr(
-                        expr,
-                        input.schema().as_ref(),
-                        ctx,
-                    )?,
-                    name.to_string(),
-                ))
-            })
-            .collect::<Result<Vec<(Arc<dyn PhysicalExpr>, String)>>>()?;
-        let proj_exprs: Vec<ProjectionExpr> = exprs
-            .into_iter()
-            .map(|(expr, alias)| ProjectionExpr { expr, alias })
-            .collect();
-        Ok(Arc::new(ProjectionExec::try_new(proj_exprs, input)?))
+        let decoder = ConverterPlanDecoder {
+            ctx,
+            proto_converter,
+        };
+        let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+        // `try_from_proto` takes the enclosing `PhysicalPlanNode`, while this
+        // deprecated method is driven by the `ProjectionExecNode` argument.
+        // Re-wrap the argument so the decoded plan keeps depending on it 
rather
+        // than on `self`, which a caller may not have kept in sync.
+        let node = protobuf::PhysicalPlanNode {
+            physical_plan_type: Some(PhysicalPlanType::Projection(Box::new(
+                projection.clone(),
+            ))),
+        };
+        ProjectionExec::try_from_proto(&node, &decode_ctx)
     }
 
     #[deprecated(
@@ -2777,31 +2770,13 @@ pub trait PhysicalPlanNodeExt: Sized {
         codec: &dyn PhysicalExtensionCodec,
         proto_converter: &dyn PhysicalProtoConverterExtension,
     ) -> Result<protobuf::PhysicalPlanNode> {
-        let input = 
protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
-            exec.input().to_owned(),
+        let encoder = ConverterPlanEncoder {
             codec,
             proto_converter,
-        )?;
-        let expr = exec
-            .expr()
-            .iter()
-            .map(|proj_expr| {
-                proto_converter.physical_expr_to_proto(&proj_expr.expr, codec)
-            })
-            .collect::<Result<Vec<_>>>()?;
-        let expr_name = exec
-            .expr()
-            .iter()
-            .map(|proj_expr| proj_expr.alias.clone())
-            .collect();
-        Ok(protobuf::PhysicalPlanNode {
-            physical_plan_type: Some(PhysicalPlanType::Projection(Box::new(
-                protobuf::ProjectionExecNode {
-                    input: Some(Box::new(input)),
-                    expr,
-                    expr_name,
-                },
-            ))),
+        };
+        let ctx = ExecutionPlanEncodeCtx::new(&encoder);
+        exec.try_to_proto(&ctx)?.ok_or_else(|| {
+            internal_datafusion_err!("ProjectionExec::try_to_proto returned 
None")
         })
     }
 
diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs 
b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
index 2671f4d015..89647d5da3 100644
--- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
+++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
@@ -2456,6 +2456,64 @@ async fn roundtrip_physical_plan_node() {
     let _ = plan.execute(0, ctx.task_ctx()).unwrap();
 }
 
+/// The deprecated `try_into_projection_physical_plan` shim now delegates to
+/// [`ProjectionExec::try_from_proto`], which reads the enclosing
+/// `PhysicalPlanNode` rather than a `ProjectionExecNode`. Assert the shim 
still
+/// decodes the node passed as an argument, not `self`, so an out-of-tree 
caller
+/// that passes a projection unrelated to `self` keeps the old behaviour.
+#[test]
+fn deprecated_projection_shim_decodes_argument_not_self() -> Result<()> {
+    use datafusion_proto::protobuf::PhysicalPlanNode;
+    use datafusion_proto::protobuf::physical_plan_node::PhysicalPlanType;
+
+    let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
+    let input = Arc::new(EmptyExec::new(Arc::new(schema.clone())));
+    let projection = Arc::new(ProjectionExec::try_new(
+        vec![ProjectionExpr::new(
+            col("a", &schema)?,
+            "renamed".to_string(),
+        )],
+        input,
+    )?);
+
+    let codec = DefaultPhysicalExtensionCodec {};
+    let proto_converter = DefaultPhysicalProtoConverter {};
+    let projection_node = 
PhysicalPlanNode::try_from_physical_plan_with_converter(
+        projection,
+        &codec,
+        &proto_converter,
+    )?;
+    let Some(PhysicalPlanType::Projection(projection_exec_node)) =
+        &projection_node.physical_plan_type
+    else {
+        panic!("expected a Projection node, got {projection_node:?}");
+    };
+
+    // `self` is deliberately a different plan variant than the argument.
+    let unrelated_node = 
PhysicalPlanNode::try_from_physical_plan_with_converter(
+        Arc::new(EmptyExec::new(Arc::new(schema))),
+        &codec,
+        &proto_converter,
+    )?;
+
+    let session_ctx = SessionContext::new();
+    let task_ctx = session_ctx.task_ctx();
+    let decode_ctx = PhysicalPlanDecodeContext::new(task_ctx.as_ref(), &codec);
+    #[allow(deprecated)]
+    let decoded = unrelated_node.try_into_projection_physical_plan(
+        projection_exec_node,
+        &decode_ctx,
+        &proto_converter,
+    )?;
+
+    let decoded = decoded
+        .downcast_ref::<ProjectionExec>()
+        .expect("decoded plan should be a ProjectionExec");
+    assert_eq!(decoded.expr().len(), 1);
+    assert_eq!(decoded.expr()[0].alias, "renamed");
+    Ok(())
+}
+
 /// Helper function to create a SessionContext with all TPC-H tables 
registered as external tables
 async fn tpch_context() -> Result<SessionContext> {
     use datafusion_common::test_util::datafusion_test_data;


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

Reply via email to