Uh oh!
There was an error while loading. Please reload this page.
feat(proto): thread expr encode/decode context into try_encode_expr / try_decode_expr - #23733
Merged
Conversation
… try_decode_expr Custom `PhysicalExpr` extension codecs whose proto embeds nested `PhysicalExprNode` fields inside their own blob could not participate in the `DynamicFilterPhysicalExpr` dedup pipeline (apache#21807): the only helper available was the free `serialize_physical_expr` / `parse_physical_expr`, hardwired to `DefaultPhysicalProtoConverter`, so nested exprs got `expr_id: None` on the wire and reconstructed as distinct `Arc`s. Rather than thread the raw `PhysicalProtoConverterExtension` (which also leaves the codec without the active schema/registry, forcing it to fabricate a `SessionContext`), pass the same `PhysicalExprEncodeCtx` / `PhysicalExprDecodeCtx` that migrated built-in exprs already receive via `try_to_proto` / `try_from_proto` (apache#22418). The codec calls `ctx.encode_child(..)` / `ctx.decode(..)`, which route through the active dedup-aware converter AND carry the real schema and task context — so nested UDF/column references resolve correctly and shared inner expressions cache-hit on their `expr_id`. This keeps the extension escape-hatch consistent with the per-expr proto hooks and avoids introducing a third context parameter on the codec API. Breaking change: downstream codecs overriding `try_encode_expr` / `try_decode_expr` must add the new `ctx` parameter (name it `_ctx` if the custom expr carries no nested `PhysicalExprNode`s). Wire format unchanged. Closesapache#22920. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6o26tZfSs2xzUWR2BWHJw
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 |
codecov-commenter
commented
Jul 21, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #23733 +/- ##
========================================
Coverage 80.70% 80.70% ========================================
Files 1089 1089 Lines 368137 368280 +143 Branches 368137 368280 +143 ========================================
+ Hits 297121 297238 +117 - Misses 53311 53324 +13 - Partials 17705 17718 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
zhuqi-lucas
approved these changes
Jul 21, 2026
zhuqi-lucas
left a comment
Contributor
There was a problem hiding this comment.
LGTM, thanks @adriangb !
Uh oh!
There was an error while loading. Please reload this page.
kosiew pushed a commit
to kosiew/datafusion
that referenced
this pull request
Aug 12, 2026
… try_decode_expr (apache#23733) ## Which issue does this PR close? Closesapache#22920. Alternative to apache#22922 — same goal, but plumbs the per-expr encode/decode **context** (from the apache#22418 hook machinery) instead of the raw `PhysicalProtoConverterExtension`. ## Rationale for this change apache#21807 introduced the `DynamicFilterPhysicalExpr` dedup pipeline so identical references on the wire reconstruct to one shared `Arc<Inner>` via `expr_id` cache keys, and apache#22011 hooked it through the `SortExec` / `AggregateExec` / `HashJoinExec` plan codecs. The remaining gap is the **expression-level** extension path. In `serialize_physical_expr_with_converter` / `parse_physical_expr_with_converter`, the codec's `try_encode_expr` / `try_decode_expr` is reached only as a fallback after the built-in `try_to_proto` path and the `ScalarFunctionExpr` downcast — i.e. only for downstream-defined **custom `PhysicalExpr`** types. When such a codec serializes nested `PhysicalExprNode` fields *inside its own blob*, the only helper available today is the free `serialize_physical_expr` / `parse_physical_expr`, hardwired to `DefaultPhysicalProtoConverter`. So those nested exprs get `expr_id: None` on the wire and reconstruct as **distinct** `Inner` allocations — heap-max updates from a `SortExec` never reach the wrapped reference. ## What changes are included in this PR? The built-in expressions migrated under apache#22418 already receive a `PhysicalExprEncodeCtx` / `PhysicalExprDecodeCtx` in their `try_to_proto` / `try_from_proto` hooks. Those context objects bundle the dedup-aware converter **plus** the active schema and task context, and hide `PhysicalProtoConverterExtension` / `PhysicalExtensionCodec` from the expression author entirely. This PR hands the **same context** to the expr-level codec methods: ```rust fn try_decode_expr( &self, buf: &[u8], inputs: &[Arc<dyn PhysicalExpr>], ctx: &PhysicalExprDecodeCtx<'_>, // new ) -> Result<Arc<dyn PhysicalExpr>>; fn try_encode_expr( &self, node: &Arc<dyn PhysicalExpr>, buf: &mut Vec<u8>, ctx: &PhysicalExprEncodeCtx<'_>, // new ) -> Result<()>; ``` A codec that embeds nested `PhysicalExprNode`s now decodes them with `ctx.decode(..)` and encodes them with `ctx.encode_child(..)`, which: - route through any active `DeduplicatingProtoConverter` / `DeduplicatingDeserializer`, so shared inner expressions cache-hit on `expr_id`; **and** - carry the real schema and task context, so nested UDF / column references resolve against the actual registry. ### Why the context, not the raw converter (cf. apache#22922) Threading the bare `PhysicalProtoConverterExtension` still leaves the codec without the schema/registry, forcing it to fabricate a `SessionContext::new()` and hard-code a schema to call `proto_to_physical_expr` — an empty-registry footgun for any nested expr that references a UDF or column. Passing the existing `Physical{Encode,Decode}Ctx` avoids that, keeps the extension escape-hatch consistent with the per-expr proto hooks, and adds no third converter parameter to the codec API (the concern raised on apache#22922). ## Are these changes tested? Yes — `extension_codec_expr_participates_in_deduplication` builds a `BinaryExpr` whose left operand is a bare `DynamicFilterPhysicalExpr` and whose right operand is a custom `WrapperExpr` whose codec embeds the same dynamic filter inside its serialized blob (via `ctx.encode_child`). After a `DeduplicatingProtoConverter` roundtrip, an `update()` on the bare-side decoded filter is observed via `current()` on the wrapped-side filter, proving both refs back the same `Inner`. The codec needs no fabricated `SessionContext` — it decodes the nested expr through `ctx.decode`. ## Are there any user-facing changes? Yes — a **breaking change** for downstream codecs that override `try_encode_expr` / `try_decode_expr`: they must add the new `ctx` parameter (name it `_ctx` if the custom expr carries no nested `PhysicalExprNode`s). Codecs that only override the plan-level `try_encode` / `try_decode` are unaffected. Wire format is unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Closes#22920.
Alternative to #22922 — same goal, but plumbs the per-expr encode/decode context (from the #22418 hook machinery) instead of the raw
PhysicalProtoConverterExtension.Rationale for this change
#21807 introduced the
DynamicFilterPhysicalExprdedup pipeline so identical references on the wire reconstruct to one sharedArc<Inner>viaexpr_idcache keys, and #22011 hooked it through theSortExec/AggregateExec/HashJoinExecplan codecs.The remaining gap is the expression-level extension path. In
serialize_physical_expr_with_converter/parse_physical_expr_with_converter, the codec'stry_encode_expr/try_decode_expris reached only as a fallback after the built-intry_to_protopath and theScalarFunctionExprdowncast — i.e. only for downstream-defined customPhysicalExprtypes. When such a codec serializes nestedPhysicalExprNodefields inside its own blob, the only helper available today is the freeserialize_physical_expr/parse_physical_expr, hardwired toDefaultPhysicalProtoConverter. So those nested exprs getexpr_id: Noneon the wire and reconstruct as distinctInnerallocations — heap-max updates from aSortExecnever reach the wrapped reference.What changes are included in this PR?
The built-in expressions migrated under #22418 already receive a
PhysicalExprEncodeCtx/PhysicalExprDecodeCtxin theirtry_to_proto/try_from_protohooks. Those context objects bundle the dedup-aware converter plus the active schema and task context, and hidePhysicalProtoConverterExtension/PhysicalExtensionCodecfrom the expression author entirely.This PR hands the same context to the expr-level codec methods:
A codec that embeds nested
PhysicalExprNodes now decodes them withctx.decode(..)and encodes them withctx.encode_child(..), which:DeduplicatingProtoConverter/DeduplicatingDeserializer, so shared inner expressions cache-hit onexpr_id; andWhy the context, not the raw converter (cf. #22922)
Threading the bare
PhysicalProtoConverterExtensionstill leaves the codec without the schema/registry, forcing it to fabricate aSessionContext::new()and hard-code a schema to callproto_to_physical_expr— an empty-registry footgun for any nested expr that references a UDF or column. Passing the existingPhysical{Encode,Decode}Ctxavoids that, keeps the extension escape-hatch consistent with the per-expr proto hooks, and adds no third converter parameter to the codec API (the concern raised on #22922).Are these changes tested?
Yes —
extension_codec_expr_participates_in_deduplicationbuilds aBinaryExprwhose left operand is a bareDynamicFilterPhysicalExprand whose right operand is a customWrapperExprwhose codec embeds the same dynamic filter inside its serialized blob (viactx.encode_child). After aDeduplicatingProtoConverterroundtrip, anupdate()on the bare-side decoded filter is observed viacurrent()on the wrapped-side filter, proving both refs back the sameInner. The codec needs no fabricatedSessionContext— it decodes the nested expr throughctx.decode.Are there any user-facing changes?
Yes — a breaking change for downstream codecs that override
try_encode_expr/try_decode_expr: they must add the newctxparameter (name it_ctxif the custom expr carries no nestedPhysicalExprNodes). Codecs that only override the plan-leveltry_encode/try_decodeare unaffected. Wire format is unchanged.🤖 Generated with Claude Code