adriangb opened a new pull request, #23421:
URL: https://github.com/apache/datafusion/pull/23421

   ## Which issue does this PR close?
   
   - Closes #22430.
   
   Part of epic #22418. Alternative to #23415 — see "Rationale" for the 
relationship.
   
   ## Rationale for this change
   
   `ScalarFunctionExpr` is the last expression on the central proto downcast 
chain whose serialization needs session-level objects: the embedded `ScalarUDF` 
must go through `PhysicalExtensionCodec` on encode and resolve via codec + 
function registry on decode, and the reconstructed expression needs the 
session's `ConfigOptions`.
   
   That makes it structurally different from the expressions migrated so far. 
The `try_to_proto` / `try_from_proto` contexts live in 
`datafusion-physical-expr-common`, which sits *below* `datafusion-expr` in the 
crate graph (`datafusion-expr` depends on it for `Arc<dyn PhysicalExpr>` in the 
UDAF/UDWF APIs), so the contexts cannot traffic in `ScalarUDF` without a 
dependency cycle. #23415 resolves that tension with type erasure (`&dyn Any` / 
`Arc<dyn Any>` on the ctx traits, plus a `config_options()` default that 
fabricates `ConfigOptions::default()`); I'd rather not stabilize either of 
those in a public API, and the fact that only `datafusion-proto` can ever back 
those trait methods is the tell that this expression shouldn't ride the 
polymorphic hook at all.
   
   This PR draws that line explicitly and keeps everything typed:
   
   - **New `ScalarUdfCodec` trait in `datafusion-expr`** (the crate that owns 
`ScalarUDF`): byte-level, format-agnostic UDF (de)serialization — 
`try_encode_udf(&ScalarUDF) -> Result<Option<Vec<u8>>>`, `try_decode_udf(name, 
payload) -> Result<Arc<ScalarUDF>>`. No proto dependency.
   - **`ScalarFunctionExpr::to_proto` / `try_from_proto` as typed inherent 
constructors** — colocated with the type exactly as the epic wants, taking the 
existing child-expr contexts *plus* `&dyn ScalarUdfCodec` and 
`Arc<ConfigOptions>`. `ScalarFunctionExpr` does **not** implement the 
`PhysicalExpr::try_to_proto` hook.
   - **`datafusion-proto` keeps one small dispatch arm on each side**, now 
containing only an adapter (`ExtensionScalarUdfCodec`, wrapping 
`PhysicalExtensionCodec` + the task context's `FunctionRegistry` with the same 
lookup order as before) and a call to the constructor. The ~30-line inline 
bodies are deleted.
   
   This mirrors how aggregate/window UDF expressions are already handled (their 
codec resolution stays in `datafusion-proto`; they were excluded from #22418 
for exactly this reason), and `ScalarUdfCodec` is the template for typed 
`FunctionCodec` variants if those expressions migrate later. It also sets up a 
future dedup: `LogicalExtensionCodec` and `PhysicalExtensionCodec` currently 
duplicate their `try_{en,de}code_udf/udaf/udwf` methods, which could eventually 
be factored onto shared typed codec traits.
   
   The module docs in `physical-expr-common` are updated to state the 
architecture line: the hooks/contexts are for **self-contained** expressions; 
**session-dependent** expressions expose typed inherent constructors and are 
dispatched by the serialization layer that owns the session.
   
   ## What changes are included in this PR?
   
   Commit 1:
   - `datafusion/expr/src/udf_codec.rs` (new): the `ScalarUdfCodec` trait, 
re-exported at the crate root.
   
   Commit 2:
   - `datafusion/physical-expr/src/scalar_function.rs`: `to_proto` / 
`try_from_proto` inherent constructors (feature-gated `proto`) + a 
`proto_tests` module.
   - `datafusion/proto/src/physical_plan/mod.rs`: `ExtensionScalarUdfCodec` 
adapter (codec + optional registry fallback).
   - `datafusion/proto/src/physical_plan/to_proto.rs` / `from_proto.rs`: the 
`ScalarFunctionExpr` arms shrink to adapter + dispatch; inline serialization 
bodies deleted.
   - `datafusion/physical-expr-common/src/physical_expr.rs`: doc-only — records 
the self-contained vs. session-dependent line on the `proto_encode` / 
`proto_decode` modules.
   
   The wire format is unchanged: same `PhysicalScalarUdfNode` fields, 
`fun_definition` only set when the codec writes bytes, `return_field_name` from 
the stored return field (identical to the old 
`return_field(&Schema::empty())?.name()`), `expr_id: None` 
(`ScalarFunctionExpr` never overrides `expression_id`, so the old arm always 
wrote `None` here too).
   
   ## Are these changes tested?
   
   - New direct tests in `scalar_function.rs::proto_tests` (9): encode happy 
path (field-by-field), codec payload passthrough in both directions, decode 
happy path, wrong `ExprType` variant, missing `return_type`, child 
encode/decode error propagation, and UDF-codec error propagation on both sides. 
Note there is no "decode returned the wrong type" test — with a typed codec 
that failure mode is unrepresentable.
   - `cargo test -p datafusion-proto --test proto_integration`: 190 passed. 
With both inline arms deleted, scalar-UDF roundtrip coverage can only pass 
through the new constructors.
   - `cargo fmt --all` and `cargo clippy --all-targets --all-features -- -D 
warnings` are clean.
   
   ## Are there any user-facing changes?
   
   No wire-format or behavior changes. New public API: 
`datafusion_expr::udf_codec::ScalarUdfCodec` and the two inherent constructors 
on `ScalarFunctionExpr` (feature `proto`). No existing signatures change.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_018MmNo1SfPHXZNaMW26KmaG
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to