Skip to main content

datafusion_proto/physical_plan/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::any::Any;
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::fmt::Debug;
22use std::sync::Arc;
23
24use arrow::datatypes::{IntervalMonthDayNanoType, Schema, SchemaRef};
25use datafusion_catalog::memory::MemorySourceConfig;
26use datafusion_common::{
27    DataFusionError, Result, internal_datafusion_err, internal_err, not_impl_err,
28};
29use datafusion_datasource_arrow::source::ArrowSource;
30#[cfg(feature = "avro")]
31use datafusion_datasource_avro::source::AvroSource;
32use datafusion_datasource_csv::file_format::CsvSink;
33use datafusion_datasource_csv::source::CsvSource;
34use datafusion_datasource_json::file_format::JsonSink;
35use datafusion_datasource_json::source::JsonSource;
36#[cfg(feature = "parquet")]
37use datafusion_datasource_parquet::file_format::ParquetSink;
38#[cfg(feature = "parquet")]
39use datafusion_datasource_parquet::source::ParquetSource;
40use datafusion_execution::{FunctionRegistry, TaskContext};
41use datafusion_expr::physical_planning_context::ScalarSubqueryResults;
42use datafusion_expr::{AggregateUDF, HigherOrderUDF, ScalarUDF, WindowUDF};
43use datafusion_functions_table::generate_series::{
44    Empty, GenSeriesArgs, GenerateSeriesTable, GenericSeriesState, TimestampValue,
45};
46use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
47use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
48use datafusion_physical_plan::aggregates::AggregateExec;
49use datafusion_physical_plan::analyze::AnalyzeExec;
50use datafusion_physical_plan::async_func::AsyncFuncExec;
51use datafusion_physical_plan::buffer::BufferExec;
52#[expect(
53    deprecated,
54    reason = "`CoalesceBatchesExec` remains supported for protobuf compatibility"
55)]
56use datafusion_physical_plan::coalesce_batches::CoalesceBatchesExec;
57use datafusion_physical_plan::coalesce_partitions::CoalescePartitionsExec;
58use datafusion_physical_plan::coop::CooperativeExec;
59use datafusion_physical_plan::empty::EmptyExec;
60use datafusion_physical_plan::explain::ExplainExec;
61use datafusion_physical_plan::filter::FilterExec;
62use datafusion_physical_plan::joins::{
63    CrossJoinExec, HashJoinExec, NestedLoopJoinExec, SortMergeJoinExec,
64    SymmetricHashJoinExec,
65};
66use datafusion_physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
67use datafusion_physical_plan::memory::LazyMemoryExec;
68use datafusion_physical_plan::placeholder_row::PlaceholderRowExec;
69use datafusion_physical_plan::projection::ProjectionExec;
70use datafusion_physical_plan::proto::{
71    ExecutionPlanDecode, ExecutionPlanDecodeCtx, ExecutionPlanEncode,
72    ExecutionPlanEncodeCtx,
73};
74use datafusion_physical_plan::repartition::RepartitionExec;
75use datafusion_physical_plan::scalar_subquery::ScalarSubqueryExec;
76use datafusion_physical_plan::sorts::sort::SortExec;
77use datafusion_physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec;
78use datafusion_physical_plan::union::{InterleaveExec, UnionExec};
79use datafusion_physical_plan::unnest::UnnestExec;
80use datafusion_physical_plan::windows::WindowAggExec;
81use datafusion_physical_plan::{ExecutionPlan, PhysicalExpr};
82use prost::Message;
83use prost::bytes::BufMut;
84
85use crate::convert_required;
86use crate::physical_plan::from_proto::parse_physical_expr_with_converter;
87use crate::physical_plan::to_proto::serialize_physical_expr_with_converter;
88use crate::protobuf::physical_plan_node::PhysicalPlanType;
89use crate::protobuf::{self, proto_error};
90
91pub mod from_proto;
92pub mod to_proto;
93
94const HUMAN_DISPLAY_ALIAS_PREFIX: &str = "\u{1f}datafusion_human_display_alias_v1:";
95
96fn encode_human_display_alias(human_display: &str, alias: &str) -> String {
97    format!(
98        "{HUMAN_DISPLAY_ALIAS_PREFIX}{}:{alias}{human_display}",
99        alias.len()
100    )
101}
102
103#[cfg(test)]
104mod file_scan_config_serde {
105    use super::*;
106    use arrow::datatypes::{DataType, Field};
107    use datafusion_common::{Constraint, Constraints, ScalarValue, Statistics};
108    use datafusion_datasource::file::FileSource;
109    use datafusion_datasource::file_groups::FileGroup;
110    use datafusion_datasource::file_scan_config::{
111        FileScanConfig, FileScanConfigBuilder,
112    };
113    use datafusion_datasource::file_stream::FileOpener;
114    use datafusion_datasource::{PartitionedFile, TableSchema};
115    use datafusion_execution::object_store::ObjectStoreUrl;
116    use datafusion_physical_expr::expressions::Column;
117    use datafusion_physical_expr::projection::{
118        ProjectionExpr as FileProjectionExpr, ProjectionExprs as FileProjectionExprs,
119    };
120    use datafusion_physical_expr::{
121        LexOrdering, Partitioning, PhysicalSortExpr, RangePartitioning, SplitPoint,
122    };
123    use datafusion_physical_plan::metrics::ExecutionPlanMetricsSet;
124    use object_store::ObjectStore;
125
126    #[derive(Clone)]
127    struct SerdeTestSource {
128        metrics: ExecutionPlanMetricsSet,
129        table_schema: TableSchema,
130        projection: Option<FileProjectionExprs>,
131    }
132
133    impl SerdeTestSource {
134        fn new(
135            table_schema: TableSchema,
136            projection: Option<FileProjectionExprs>,
137        ) -> Self {
138            Self {
139                metrics: ExecutionPlanMetricsSet::new(),
140                table_schema,
141                projection,
142            }
143        }
144    }
145
146    impl FileSource for SerdeTestSource {
147        fn create_file_opener(
148            &self,
149            _object_store: Arc<dyn ObjectStore>,
150            _base_config: &FileScanConfig,
151            _partition: usize,
152        ) -> Result<Arc<dyn FileOpener>> {
153            internal_err!("not needed for FileScanConfig serde tests")
154        }
155
156        fn table_schema(&self) -> &TableSchema {
157            &self.table_schema
158        }
159
160        fn with_batch_size(&self, _batch_size: usize) -> Arc<dyn FileSource> {
161            Arc::new(self.clone())
162        }
163
164        fn metrics(&self) -> &ExecutionPlanMetricsSet {
165            &self.metrics
166        }
167
168        fn file_type(&self) -> &str {
169            "serde-test"
170        }
171
172        fn apply_expressions(
173            &self,
174            f: &mut dyn FnMut(
175                &Arc<dyn PhysicalExpr>,
176            )
177                -> Result<datafusion_common::tree_node::TreeNodeRecursion>,
178        ) -> Result<datafusion_common::tree_node::TreeNodeRecursion> {
179            datafusion_physical_plan::apply_expression_roots(
180                self.projection.iter().flatten(),
181                f,
182            )
183        }
184
185        fn try_pushdown_projection(
186            &self,
187            projection: &FileProjectionExprs,
188        ) -> Result<Option<Arc<dyn FileSource>>> {
189            Ok(Some(Arc::new(Self {
190                projection: Some(projection.clone()),
191                ..self.clone()
192            })))
193        }
194
195        fn projection(&self) -> Option<&FileProjectionExprs> {
196            self.projection.as_ref()
197        }
198    }
199
200    fn populated_projection() -> FileProjectionExprs {
201        FileProjectionExprs::new(vec![FileProjectionExpr::new(
202            Arc::new(Column::new("value", 0)),
203            "projected_value",
204        )])
205    }
206
207    fn test_config(output_partitioning: Option<Partitioning>) -> FileScanConfig {
208        test_config_with_projection(output_partitioning, Some(populated_projection()))
209    }
210
211    fn test_config_with_projection(
212        output_partitioning: Option<Partitioning>,
213        projection: Option<FileProjectionExprs>,
214    ) -> FileScanConfig {
215        let file_schema = Arc::new(
216            Schema::new(vec![
217                Field::new("value", DataType::Int32, false),
218                Field::new("label", DataType::Utf8, true),
219            ])
220            .with_metadata(HashMap::from([(
221                "serde_test_key".to_string(),
222                "serde_test_value".to_string(),
223            )])),
224        );
225        let table_schema = TableSchema::builder(Arc::clone(&file_schema))
226            .with_table_partition_cols(vec![Arc::new(Field::new(
227                "part",
228                DataType::Utf8,
229                false,
230            ))])
231            .build();
232        let table_statistics = Statistics::new_unknown(table_schema.table_schema());
233        let source = Arc::new(SerdeTestSource::new(table_schema, projection));
234        let first_file = PartitionedFile::new("data/part=a/file.arrow", 1024)
235            .with_partition_values(vec![ScalarValue::Utf8(Some("a".to_string()))])
236            .with_range(10, 900)
237            .with_arrow_schema(Arc::clone(&file_schema))
238            .with_statistics(Arc::new(table_statistics.clone()));
239        let second_file = PartitionedFile::new("data/part=b/file.arrow", 2048)
240            .with_partition_values(vec![ScalarValue::Utf8(Some("b".to_string()))]);
241        let third_file = PartitionedFile::new("data/part=c/file.arrow", 4096)
242            .with_partition_values(vec![ScalarValue::Utf8(Some("c".to_string()))])
243            .with_arrow_schema(Arc::clone(&file_schema));
244        let ordering = LexOrdering::new(vec![PhysicalSortExpr::new_default(Arc::new(
245            Column::new("value", 0),
246        ))])
247        .expect("single expression ordering");
248
249        FileScanConfigBuilder::new(ObjectStoreUrl::local_filesystem(), source)
250            .with_file_groups(vec![
251                FileGroup::new(vec![first_file, second_file]),
252                FileGroup::new(vec![third_file]),
253            ])
254            .with_constraints(Constraints::new_unverified(vec![Constraint::PrimaryKey(
255                vec![0],
256            )]))
257            .with_statistics(table_statistics)
258            .with_limit(Some(17))
259            .with_batch_size(Some(256))
260            .with_output_ordering(vec![ordering])
261            .with_output_partitioning(output_partitioning)
262            .build()
263    }
264
265    fn hash_partitioning() -> Partitioning {
266        Partitioning::Hash(vec![Arc::new(Column::new("value", 0))], 3)
267    }
268
269    fn range_partitioning() -> Partitioning {
270        let ordering = LexOrdering::new(vec![PhysicalSortExpr::new_default(Arc::new(
271            Column::new("value", 0),
272        ))])
273        .expect("single expression ordering");
274        Partitioning::Range(RangePartitioning::new(
275            ordering,
276            vec![SplitPoint::new(vec![ScalarValue::Int32(Some(10))])],
277        ))
278    }
279
280    fn decode_source(conf: &protobuf::FileScanExecConf) -> Result<Arc<dyn FileSource>> {
281        Ok(Arc::new(SerdeTestSource::new(
282            FileScanConfig::parse_table_schema_from_proto(conf)?,
283            None,
284        )))
285    }
286
287    struct FileScanSerdeHarness {
288        codec: DefaultPhysicalExtensionCodec,
289        converter: DefaultPhysicalProtoConverter,
290        task_ctx: TaskContext,
291    }
292
293    impl FileScanSerdeHarness {
294        fn new() -> Self {
295            Self {
296                codec: DefaultPhysicalExtensionCodec {},
297                converter: DefaultPhysicalProtoConverter {},
298                task_ctx: TaskContext::default(),
299            }
300        }
301
302        fn encode(&self, config: &FileScanConfig) -> Result<protobuf::FileScanExecConf> {
303            let encoder = ConverterPlanEncoder {
304                codec: &self.codec,
305                proto_converter: &self.converter,
306            };
307            config.try_to_proto(&ExecutionPlanEncodeCtx::new(&encoder))
308        }
309
310        fn decode(&self, conf: &protobuf::FileScanExecConf) -> Result<FileScanConfig> {
311            self.decode_with_source(conf, decode_source(conf)?)
312        }
313
314        fn decode_with_source(
315            &self,
316            conf: &protobuf::FileScanExecConf,
317            file_source: Arc<dyn FileSource>,
318        ) -> Result<FileScanConfig> {
319            let physical_decode_ctx =
320                PhysicalPlanDecodeContext::new(&self.task_ctx, &self.codec);
321            let decoder = ConverterPlanDecoder {
322                ctx: &physical_decode_ctx,
323                proto_converter: &self.converter,
324            };
325            FileScanConfig::try_from_proto(
326                conf,
327                &ExecutionPlanDecodeCtx::new(&decoder),
328                file_source,
329            )
330        }
331    }
332
333    #[test]
334    fn new_file_scan_config_serde_roundtrips_all_partitioning_variants() -> Result<()> {
335        let serde = FileScanSerdeHarness::new();
336
337        for config in [
338            test_config(None),
339            test_config(Some(Partitioning::RoundRobinBatch(2))),
340            test_config(Some(hash_partitioning())),
341            test_config(Some(range_partitioning())),
342            test_config(Some(Partitioning::UnknownPartitioning(4))),
343        ] {
344            let encoded = serde.encode(&config)?;
345            let reencoded = serde.encode(&serde.decode(&encoded)?)?;
346            assert_eq!(reencoded.output_partitioning, encoded.output_partitioning);
347        }
348
349        Ok(())
350    }
351
352    #[test]
353    fn new_file_scan_config_serde_preserves_complete_fixture() -> Result<()> {
354        let serde = FileScanSerdeHarness::new();
355        let config = test_config(None);
356        let decoded = serde.decode(&serde.encode(&config)?)?;
357
358        assert_eq!(decoded.constraints, config.constraints);
359        assert_eq!(
360            decoded.file_schema().metadata,
361            config.file_schema().metadata
362        );
363        assert_eq!(decoded.file_groups.len(), 2);
364        assert_eq!(decoded.file_groups[0].len(), 2);
365        assert_eq!(decoded.file_groups[1].len(), 1);
366        assert!(decoded.file_groups[0].files()[0].arrow_schema.is_some());
367        assert!(decoded.file_groups[0].files()[1].arrow_schema.is_none());
368
369        Ok(())
370    }
371
372    #[test]
373    fn new_file_scan_config_serde_preserves_projection_presence() -> Result<()> {
374        let serde = FileScanSerdeHarness::new();
375
376        let absent = serde.encode(&test_config_with_projection(None, None))?;
377        assert!(absent.projection_exprs.is_none());
378        assert!(serde.decode(&absent)?.file_source().projection().is_none());
379
380        let empty = serde.encode(&test_config_with_projection(
381            None,
382            Some(FileProjectionExprs::new(vec![])),
383        ))?;
384        assert!(
385            empty
386                .projection_exprs
387                .as_ref()
388                .is_some_and(|projection| projection.projections.is_empty())
389        );
390        assert!(
391            serde
392                .decode(&empty)?
393                .file_source()
394                .projection()
395                .is_some_and(|projection| projection.as_ref().is_empty())
396        );
397
398        Ok(())
399    }
400
401    #[test]
402    fn new_file_scan_config_decode_rejects_malformed_required_fields() -> Result<()> {
403        let serde = FileScanSerdeHarness::new();
404        let valid = serde.encode(&test_config(None))?;
405        let file_source = decode_source(&valid)?;
406
407        for (field, malformed) in [
408            (
409                "schema",
410                protobuf::FileScanExecConf {
411                    schema: None,
412                    ..valid.clone()
413                },
414            ),
415            (
416                "constraints",
417                protobuf::FileScanExecConf {
418                    constraints: None,
419                    ..valid.clone()
420                },
421            ),
422            (
423                "statistics",
424                protobuf::FileScanExecConf {
425                    statistics: None,
426                    ..valid.clone()
427                },
428            ),
429        ] {
430            let err = serde
431                .decode_with_source(&malformed, Arc::clone(&file_source))
432                .expect_err("missing required field must fail");
433            assert!(err.to_string().contains(field), "unexpected error: {err}");
434        }
435
436        let mut missing_projection_expr = valid.clone();
437        missing_projection_expr
438            .projection_exprs
439            .as_mut()
440            .expect("test config has projection expressions")
441            .projections[0]
442            .expr = None;
443        let err = serde
444            .decode_with_source(&missing_projection_expr, file_source)
445            .expect_err("missing projection expression must fail");
446        assert!(
447            err.to_string()
448                .contains("ProjectionExpr missing expr field"),
449            "unexpected error: {err}"
450        );
451
452        Ok(())
453    }
454
455    #[test]
456    fn new_file_scan_config_decode_rejects_invalid_range_ordering() -> Result<()> {
457        let serde = FileScanSerdeHarness::new();
458        let mut proto = serde.encode(&test_config(Some(range_partitioning())))?;
459
460        let mut duplicate_ordering = proto.clone();
461        let range = match duplicate_ordering
462            .output_partitioning
463            .as_mut()
464            .and_then(|p| p.partition_method.as_mut())
465        {
466            Some(protobuf::partitioning::PartitionMethod::Range(range)) => range,
467            other => panic!("expected range partitioning, got {other:?}"),
468        };
469        range.sort_expr.push(range.sort_expr[0].clone());
470
471        let err = serde
472            .decode(&duplicate_ordering)
473            .expect_err("duplicate range ordering must fail");
474        assert!(
475            err.to_string().contains("duplicate expressions"),
476            "unexpected error: {err}"
477        );
478
479        let range = match proto
480            .output_partitioning
481            .as_mut()
482            .and_then(|p| p.partition_method.as_mut())
483        {
484            Some(protobuf::partitioning::PartitionMethod::Range(range)) => range,
485            other => panic!("expected range partitioning, got {other:?}"),
486        };
487        range.sort_expr.clear();
488
489        let err = serde
490            .decode(&proto)
491            .expect_err("empty range ordering must fail");
492        assert!(
493            err.to_string().contains("requires non-empty ordering"),
494            "unexpected error: {err}"
495        );
496
497        Ok(())
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    /// Unit tests for the bytes-only function serde exposed on
506    /// [`ExecutionPlanEncodeCtx`] / [`ExecutionPlanDecodeCtx`] and backed by
507    /// [`ConverterPlanEncoder`] / [`ConverterPlanDecoder`]. Function-carrying
508    /// plans migrate in follow-up PRs, so these paths have no in-tree plan
509    /// caller yet; the tests pin the payload semantics (`None` == encode by
510    /// name) and the decode lookup order (payload → codec; else registry →
511    /// codec fallback with an empty buffer) that those migrations rely on.
512    mod function_serde {
513        use super::*;
514        use arrow::datatypes::{DataType, Field, FieldRef};
515        use datafusion_common::plan_err;
516        use datafusion_execution::config::SessionConfig;
517        use datafusion_execution::runtime_env::RuntimeEnv;
518        use datafusion_expr::function::AccumulatorArgs;
519        use datafusion_expr::{
520            Accumulator, AggregateUDFImpl, ColumnarValue, PartitionEvaluator,
521            ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility, WindowUDFImpl,
522        };
523        use datafusion_functions_window_common::field::WindowUDFFieldArgs;
524        use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
525
526        #[derive(Debug, PartialEq, Eq, Hash)]
527        struct TestUdf {
528            signature: Signature,
529        }
530
531        impl TestUdf {
532            fn new() -> Self {
533                Self {
534                    signature: Signature::exact(
535                        vec![DataType::Int64],
536                        Volatility::Immutable,
537                    ),
538                }
539            }
540        }
541
542        impl ScalarUDFImpl for TestUdf {
543            fn name(&self) -> &str {
544                "test_udf"
545            }
546            fn signature(&self) -> &Signature {
547                &self.signature
548            }
549            fn return_type(&self, _args: &[DataType]) -> Result<DataType> {
550                Ok(DataType::Int64)
551            }
552            fn invoke_with_args(
553                &self,
554                _args: ScalarFunctionArgs,
555            ) -> Result<ColumnarValue> {
556                plan_err!("test only")
557            }
558        }
559
560        #[derive(Debug, PartialEq, Eq, Hash)]
561        struct TestUdaf {
562            signature: Signature,
563        }
564
565        impl TestUdaf {
566            fn new() -> Self {
567                Self {
568                    signature: Signature::exact(
569                        vec![DataType::Int64],
570                        Volatility::Immutable,
571                    ),
572                }
573            }
574        }
575
576        impl AggregateUDFImpl for TestUdaf {
577            fn name(&self) -> &str {
578                "test_udaf"
579            }
580            fn signature(&self) -> &Signature {
581                &self.signature
582            }
583            fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
584                Ok(DataType::Int64)
585            }
586            fn accumulator(
587                &self,
588                _acc_args: AccumulatorArgs,
589            ) -> Result<Box<dyn Accumulator>> {
590                plan_err!("test only")
591            }
592        }
593
594        #[derive(Debug, PartialEq, Eq, Hash)]
595        struct TestUdwf {
596            signature: Signature,
597        }
598
599        impl TestUdwf {
600            fn new() -> Self {
601                Self {
602                    signature: Signature::exact(
603                        vec![DataType::Int64],
604                        Volatility::Immutable,
605                    ),
606                }
607            }
608        }
609
610        impl WindowUDFImpl for TestUdwf {
611            fn name(&self) -> &str {
612                "test_udwf"
613            }
614            fn signature(&self) -> &Signature {
615                &self.signature
616            }
617            fn partition_evaluator(
618                &self,
619                _partition_evaluator_args: PartitionEvaluatorArgs,
620            ) -> Result<Box<dyn PartitionEvaluator>> {
621                plan_err!("test only")
622            }
623            fn field(&self, field_args: WindowUDFFieldArgs) -> Result<FieldRef> {
624                Ok(Field::new(field_args.name(), DataType::Int64, true).into())
625            }
626        }
627
628        /// Codec that encodes every function as its name bytes and decodes by
629        /// checking the payload it receives, so tests can observe exactly what
630        /// crosses the bytes-only boundary.
631        #[derive(Debug)]
632        struct PayloadCodec;
633
634        impl PhysicalExtensionCodec for PayloadCodec {
635            fn try_decode(
636                &self,
637                _buf: &[u8],
638                _inputs: &[Arc<dyn ExecutionPlan>],
639                _ctx: &TaskContext,
640                _proto_converter: &dyn PhysicalProtoConverterExtension,
641            ) -> Result<Arc<dyn ExecutionPlan>> {
642                internal_err!("not needed for these tests")
643            }
644
645            fn try_encode(
646                &self,
647                _node: Arc<dyn ExecutionPlan>,
648                _buf: &mut Vec<u8>,
649                _proto_converter: &dyn PhysicalProtoConverterExtension,
650            ) -> Result<()> {
651                internal_err!("not needed for these tests")
652            }
653
654            fn try_encode_udf(&self, node: &ScalarUDF, buf: &mut Vec<u8>) -> Result<()> {
655                buf.extend_from_slice(node.name().as_bytes());
656                Ok(())
657            }
658
659            fn try_decode_udf(&self, name: &str, buf: &[u8]) -> Result<Arc<ScalarUDF>> {
660                assert_eq!(name, "test_udf");
661                assert_eq!(buf, name.as_bytes());
662                Ok(Arc::new(ScalarUDF::from(TestUdf::new())))
663            }
664
665            fn try_encode_udaf(
666                &self,
667                node: &AggregateUDF,
668                buf: &mut Vec<u8>,
669            ) -> Result<()> {
670                buf.extend_from_slice(node.name().as_bytes());
671                Ok(())
672            }
673
674            fn try_decode_udaf(
675                &self,
676                name: &str,
677                buf: &[u8],
678            ) -> Result<Arc<AggregateUDF>> {
679                assert_eq!(name, "test_udaf");
680                assert_eq!(buf, name.as_bytes());
681                Ok(Arc::new(AggregateUDF::from(TestUdaf::new())))
682            }
683
684            fn try_encode_udwf(&self, node: &WindowUDF, buf: &mut Vec<u8>) -> Result<()> {
685                buf.extend_from_slice(node.name().as_bytes());
686                Ok(())
687            }
688
689            fn try_decode_udwf(&self, name: &str, buf: &[u8]) -> Result<Arc<WindowUDF>> {
690                assert_eq!(name, "test_udwf");
691                assert_eq!(buf, name.as_bytes());
692                Ok(Arc::new(WindowUDF::from(TestUdwf::new())))
693            }
694        }
695
696        /// Codec whose decode hooks only accept an empty payload, to pin the
697        /// by-name decode fallback (registry miss → codec with `&[]`).
698        #[derive(Debug)]
699        struct EmptyPayloadOnlyCodec;
700
701        impl PhysicalExtensionCodec for EmptyPayloadOnlyCodec {
702            fn try_decode(
703                &self,
704                _buf: &[u8],
705                _inputs: &[Arc<dyn ExecutionPlan>],
706                _ctx: &TaskContext,
707                _proto_converter: &dyn PhysicalProtoConverterExtension,
708            ) -> Result<Arc<dyn ExecutionPlan>> {
709                internal_err!("not needed for these tests")
710            }
711
712            fn try_encode(
713                &self,
714                _node: Arc<dyn ExecutionPlan>,
715                _buf: &mut Vec<u8>,
716                _proto_converter: &dyn PhysicalProtoConverterExtension,
717            ) -> Result<()> {
718                internal_err!("not needed for these tests")
719            }
720
721            fn try_decode_udf(&self, _name: &str, buf: &[u8]) -> Result<Arc<ScalarUDF>> {
722                assert!(buf.is_empty());
723                Ok(Arc::new(ScalarUDF::from(TestUdf::new())))
724            }
725
726            fn try_decode_udaf(
727                &self,
728                _name: &str,
729                buf: &[u8],
730            ) -> Result<Arc<AggregateUDF>> {
731                assert!(buf.is_empty());
732                Ok(Arc::new(AggregateUDF::from(TestUdaf::new())))
733            }
734
735            fn try_decode_udwf(&self, _name: &str, buf: &[u8]) -> Result<Arc<WindowUDF>> {
736                assert!(buf.is_empty());
737                Ok(Arc::new(WindowUDF::from(TestUdwf::new())))
738            }
739        }
740
741        fn encode_ctx_over<'a>(
742            codec: &'a dyn PhysicalExtensionCodec,
743            proto_converter: &'a dyn PhysicalProtoConverterExtension,
744        ) -> ConverterPlanEncoder<'a> {
745            ConverterPlanEncoder {
746                codec,
747                proto_converter,
748            }
749        }
750
751        #[test]
752        fn encode_by_name_functions_produce_no_payload() -> Result<()> {
753            let codec = DefaultPhysicalExtensionCodec {};
754            let converter = DefaultPhysicalProtoConverter {};
755            let encoder = encode_ctx_over(&codec, &converter);
756            let ctx = ExecutionPlanEncodeCtx::new(&encoder);
757
758            assert!(ctx.encode_udf(&ScalarUDF::from(TestUdf::new()))?.is_none());
759            assert!(
760                ctx.encode_udaf(&AggregateUDF::from(TestUdaf::new()))?
761                    .is_none()
762            );
763            assert!(
764                ctx.encode_udwf(&WindowUDF::from(TestUdwf::new()))?
765                    .is_none()
766            );
767            Ok(())
768        }
769
770        #[test]
771        fn encode_functions_surface_codec_payload() -> Result<()> {
772            let codec = PayloadCodec;
773            let converter = DefaultPhysicalProtoConverter {};
774            let encoder = encode_ctx_over(&codec, &converter);
775            let ctx = ExecutionPlanEncodeCtx::new(&encoder);
776
777            assert_eq!(
778                ctx.encode_udf(&ScalarUDF::from(TestUdf::new()))?.as_deref(),
779                Some(b"test_udf".as_slice())
780            );
781            assert_eq!(
782                ctx.encode_udaf(&AggregateUDF::from(TestUdaf::new()))?
783                    .as_deref(),
784                Some(b"test_udaf".as_slice())
785            );
786            assert_eq!(
787                ctx.encode_udwf(&WindowUDF::from(TestUdwf::new()))?
788                    .as_deref(),
789                Some(b"test_udwf".as_slice())
790            );
791            Ok(())
792        }
793
794        #[test]
795        fn decode_functions_prefer_explicit_payload() -> Result<()> {
796            let task_ctx = TaskContext::default();
797            let codec = PayloadCodec;
798            let decode_context = PhysicalPlanDecodeContext::new(&task_ctx, &codec);
799            let converter = DefaultPhysicalProtoConverter {};
800            let decoder = ConverterPlanDecoder {
801                ctx: &decode_context,
802                proto_converter: &converter,
803            };
804            let ctx = ExecutionPlanDecodeCtx::new(&decoder);
805
806            assert_eq!(
807                ctx.decode_udf("test_udf", Some(b"test_udf"))?.name(),
808                "test_udf"
809            );
810            assert_eq!(
811                ctx.decode_udaf("test_udaf", Some(b"test_udaf"))?.name(),
812                "test_udaf"
813            );
814            assert_eq!(
815                ctx.decode_udwf("test_udwf", Some(b"test_udwf"))?.name(),
816                "test_udwf"
817            );
818            Ok(())
819        }
820
821        #[test]
822        fn decode_functions_by_name_resolve_from_registry() -> Result<()> {
823            let udf = Arc::new(ScalarUDF::from(TestUdf::new()));
824            let udaf = Arc::new(AggregateUDF::from(TestUdaf::new()));
825            let udwf = Arc::new(WindowUDF::from(TestUdwf::new()));
826            let task_ctx = TaskContext::new(
827                None,
828                "test".to_string(),
829                SessionConfig::new(),
830                HashMap::from([("test_udf".to_string(), Arc::clone(&udf))]),
831                HashMap::new(),
832                HashMap::from([("test_udaf".to_string(), Arc::clone(&udaf))]),
833                HashMap::from([("test_udwf".to_string(), Arc::clone(&udwf))]),
834                Arc::new(RuntimeEnv::default()),
835            );
836            // The default codec fails any decode, so a success proves the
837            // registry satisfied the lookup without a codec fallback.
838            let codec = DefaultPhysicalExtensionCodec {};
839            let decode_context = PhysicalPlanDecodeContext::new(&task_ctx, &codec);
840            let converter = DefaultPhysicalProtoConverter {};
841            let decoder = ConverterPlanDecoder {
842                ctx: &decode_context,
843                proto_converter: &converter,
844            };
845            let ctx = ExecutionPlanDecodeCtx::new(&decoder);
846
847            assert!(Arc::ptr_eq(&ctx.decode_udf("test_udf", None)?, &udf));
848            assert!(Arc::ptr_eq(&ctx.decode_udaf("test_udaf", None)?, &udaf));
849            assert!(Arc::ptr_eq(&ctx.decode_udwf("test_udwf", None)?, &udwf));
850            assert_eq!(ctx.task_ctx().session_id(), "test");
851            Ok(())
852        }
853
854        #[test]
855        fn decode_functions_by_name_fall_back_to_codec_on_registry_miss() -> Result<()> {
856            let task_ctx = TaskContext::default();
857            let codec = EmptyPayloadOnlyCodec;
858            let decode_context = PhysicalPlanDecodeContext::new(&task_ctx, &codec);
859            let converter = DefaultPhysicalProtoConverter {};
860            let decoder = ConverterPlanDecoder {
861                ctx: &decode_context,
862                proto_converter: &converter,
863            };
864            let ctx = ExecutionPlanDecodeCtx::new(&decoder);
865
866            assert_eq!(ctx.decode_udf("test_udf", None)?.name(), "test_udf");
867            assert_eq!(ctx.decode_udaf("test_udaf", None)?.name(), "test_udaf");
868            assert_eq!(ctx.decode_udwf("test_udwf", None)?.name(), "test_udwf");
869            Ok(())
870        }
871
872        #[test]
873        fn decode_required_helpers_error_on_missing_fields() {
874            let task_ctx = TaskContext::default();
875            let codec = DefaultPhysicalExtensionCodec {};
876            let decode_context = PhysicalPlanDecodeContext::new(&task_ctx, &codec);
877            let converter = DefaultPhysicalProtoConverter {};
878            let decoder = ConverterPlanDecoder {
879                ctx: &decode_context,
880                proto_converter: &converter,
881            };
882            let ctx = ExecutionPlanDecodeCtx::new(&decoder);
883
884            let err = ctx
885                .decode_required_child(None, "FooExec", "input")
886                .unwrap_err();
887            assert!(
888                err.to_string()
889                    .contains("FooExec is missing required field 'input'"),
890                "unexpected error: {err}"
891            );
892
893            let schema = Schema::empty();
894            let err = ctx
895                .decode_required_expr(None, &schema, "FooExec", "predicate")
896                .unwrap_err();
897            assert!(
898                err.to_string()
899                    .contains("FooExec is missing required field 'predicate'"),
900                "unexpected error: {err}"
901            );
902        }
903
904        #[test]
905        fn try_from_proto_rejects_wrong_plan_variant() {
906            let task_ctx = TaskContext::default();
907            let codec = DefaultPhysicalExtensionCodec {};
908            let decode_context = PhysicalPlanDecodeContext::new(&task_ctx, &codec);
909            let converter = DefaultPhysicalProtoConverter {};
910            let decoder = ConverterPlanDecoder {
911                ctx: &decode_context,
912                proto_converter: &converter,
913            };
914            let ctx = ExecutionPlanDecodeCtx::new(&decoder);
915
916            let node = protobuf::PhysicalPlanNode {
917                physical_plan_type: None,
918            };
919            let err = ProjectionExec::try_from_proto(&node, &ctx).unwrap_err();
920            assert!(
921                err.to_string()
922                    .contains("PhysicalPlanNode is not a ProjectionExec"),
923                "unexpected error: {err}"
924            );
925        }
926    }
927}
928
929/// Context threaded through physical-plan deserialization.
930///
931/// This bundles the stable per-call inputs for deserialization and the
932/// per-scope `ScalarSubqueryResults` handle needed while reconstructing
933/// `ScalarSubqueryExpr` nodes inside a `ScalarSubqueryExec` input plan.
934#[derive(Clone)]
935pub struct PhysicalPlanDecodeContext<'a> {
936    task_ctx: &'a TaskContext,
937    codec: &'a dyn PhysicalExtensionCodec,
938    scalar_subquery_results: Option<ScalarSubqueryResults>,
939}
940
941impl<'a> PhysicalPlanDecodeContext<'a> {
942    /// Creates a new root decode context.
943    pub fn new(task_ctx: &'a TaskContext, codec: &'a dyn PhysicalExtensionCodec) -> Self {
944        Self {
945            task_ctx,
946            codec,
947            scalar_subquery_results: None,
948        }
949    }
950
951    /// Returns the task context used for deserialization.
952    pub fn task_ctx(&self) -> &'a TaskContext {
953        self.task_ctx
954    }
955
956    /// Returns the physical extension codec used for deserialization.
957    pub fn codec(&self) -> &'a dyn PhysicalExtensionCodec {
958        self.codec
959    }
960
961    /// Returns the scalar subquery results container for the current scope, if
962    /// one is active.
963    pub fn scalar_subquery_results(&self) -> Option<&ScalarSubqueryResults> {
964        self.scalar_subquery_results.as_ref()
965    }
966
967    /// Returns a child context with a different scalar subquery results
968    /// container.
969    pub fn with_scalar_subquery_results(
970        &self,
971        scalar_subquery_results: ScalarSubqueryResults,
972    ) -> Self {
973        Self {
974            task_ctx: self.task_ctx,
975            codec: self.codec,
976            scalar_subquery_results: Some(scalar_subquery_results),
977        }
978    }
979}
980
981impl AsExecutionPlan for protobuf::PhysicalPlanNode {
982    fn try_decode(buf: &[u8]) -> Result<Self>
983    where
984        Self: Sized,
985    {
986        protobuf::PhysicalPlanNode::decode(buf).map_err(|e| {
987            internal_datafusion_err!("failed to decode physical plan: {e:?}")
988        })
989    }
990
991    fn try_encode<B>(&self, buf: &mut B) -> Result<()>
992    where
993        B: BufMut,
994        Self: Sized,
995    {
996        self.encode(buf).map_err(|e| {
997            internal_datafusion_err!("failed to encode physical plan: {e:?}")
998        })
999    }
1000
1001    fn try_into_physical_plan(
1002        &self,
1003        ctx: &TaskContext,
1004        codec: &dyn PhysicalExtensionCodec,
1005    ) -> Result<Arc<dyn ExecutionPlan>> {
1006        self.try_into_physical_plan_with_converter(
1007            ctx,
1008            codec,
1009            &DefaultPhysicalProtoConverter {},
1010        )
1011    }
1012
1013    fn try_from_physical_plan(
1014        plan: Arc<dyn ExecutionPlan>,
1015        codec: &dyn PhysicalExtensionCodec,
1016    ) -> Result<Self>
1017    where
1018        Self: Sized,
1019    {
1020        Self::try_from_physical_plan_with_converter(
1021            plan,
1022            codec,
1023            &DefaultPhysicalProtoConverter {},
1024        )
1025    }
1026}
1027
1028/// Extension methods on [`protobuf::PhysicalPlanNode`].
1029///
1030/// The prost-generated `PhysicalPlanNode` struct lives in
1031/// `datafusion-proto-models`, which is foreign to this crate, so the orphan
1032/// rule forbids inherent `impl` blocks here. Instead, all (de)serialization
1033/// helpers are exposed through this trait. Callers can bring it in scope with
1034/// `use datafusion_proto::physical_plan::PhysicalPlanNodeExt;`.
1035///
1036/// Method bodies live in the default trait implementation. To make the trait
1037/// usable as if it were inherent (i.e. let bodies access fields on `self`),
1038/// implementors provide [`PhysicalPlanNodeExt::node`] returning a reference
1039/// back to the concrete `protobuf::PhysicalPlanNode`. Default method bodies
1040/// then go through `self.node()` to read fields.
1041pub trait PhysicalPlanNodeExt: Sized {
1042    /// Returns a reference to the underlying [`protobuf::PhysicalPlanNode`].
1043    fn node(&self) -> &protobuf::PhysicalPlanNode;
1044
1045    fn try_into_physical_plan_with_converter(
1046        &self,
1047        ctx: &TaskContext,
1048        codec: &dyn PhysicalExtensionCodec,
1049        proto_converter: &dyn PhysicalProtoConverterExtension,
1050    ) -> Result<Arc<dyn ExecutionPlan>> {
1051        let decode_ctx = PhysicalPlanDecodeContext::new(ctx, codec);
1052        self.try_into_physical_plan_with_context(&decode_ctx, proto_converter)
1053    }
1054
1055    fn try_into_physical_plan_with_context(
1056        &self,
1057        ctx: &PhysicalPlanDecodeContext<'_>,
1058        proto_converter: &dyn PhysicalProtoConverterExtension,
1059    ) -> Result<Arc<dyn ExecutionPlan>> {
1060        let plan = self.node().physical_plan_type.as_ref().ok_or_else(|| {
1061            proto_error(format!(
1062                "physical_plan::from_proto() Unsupported physical plan '{:?}'",
1063                self.node(),
1064            ))
1065        })?;
1066        // Decode context for plans migrated to the `try_from_proto` pattern
1067        // (#22419). Arms for migrated plans are one-liners delegating to the
1068        // plan's own crate; un-migrated arms keep their inline bodies.
1069        let plan_decoder = ConverterPlanDecoder {
1070            ctx,
1071            proto_converter,
1072        };
1073        let decode_ctx = ExecutionPlanDecodeCtx::new(&plan_decoder);
1074        match plan {
1075            PhysicalPlanType::Explain(_) => {
1076                ExplainExec::try_from_proto(self.node(), &decode_ctx)
1077            }
1078            PhysicalPlanType::Projection(_) => {
1079                ProjectionExec::try_from_proto(self.node(), &decode_ctx)
1080            }
1081            PhysicalPlanType::Filter(_) => {
1082                FilterExec::try_from_proto(self.node(), &decode_ctx)
1083            }
1084            PhysicalPlanType::CsvScan(_) => {
1085                CsvSource::try_from_proto(self.node(), &decode_ctx)
1086            }
1087            PhysicalPlanType::JsonScan(_) => {
1088                JsonSource::try_from_proto(self.node(), &decode_ctx)
1089            }
1090            PhysicalPlanType::ParquetScan(_) => {
1091                #[cfg(feature = "parquet")]
1092                {
1093                    ParquetSource::try_from_proto(self.node(), &decode_ctx)
1094                }
1095                #[cfg(not(feature = "parquet"))]
1096                not_impl_err!(
1097                    "Unable to process a Parquet PhysicalPlan when the `parquet` feature is not enabled"
1098                )
1099            }
1100            PhysicalPlanType::AvroScan(_) => {
1101                #[cfg(feature = "avro")]
1102                {
1103                    AvroSource::try_from_proto(self.node(), &decode_ctx)
1104                }
1105                #[cfg(not(feature = "avro"))]
1106                panic!(
1107                    "Unable to process a Avro PhysicalPlan when `avro` feature is not enabled"
1108                )
1109            }
1110            PhysicalPlanType::MemoryScan(_) => {
1111                MemorySourceConfig::try_from_proto(self.node(), &decode_ctx)
1112            }
1113            PhysicalPlanType::ArrowScan(_) => {
1114                ArrowSource::try_from_proto(self.node(), &decode_ctx)
1115            }
1116            #[expect(
1117                deprecated,
1118                reason = "`CoalesceBatchesExec` remains supported for protobuf compatibility"
1119            )]
1120            PhysicalPlanType::CoalesceBatches(_) => {
1121                CoalesceBatchesExec::try_from_proto(self.node(), &decode_ctx)
1122            }
1123            PhysicalPlanType::Merge(_) => {
1124                CoalescePartitionsExec::try_from_proto(self.node(), &decode_ctx)
1125            }
1126            PhysicalPlanType::Repartition(_) => {
1127                RepartitionExec::try_from_proto(self.node(), &decode_ctx)
1128            }
1129            PhysicalPlanType::GlobalLimit(_) => {
1130                GlobalLimitExec::try_from_proto(self.node(), &decode_ctx)
1131            }
1132            PhysicalPlanType::LocalLimit(_) => {
1133                LocalLimitExec::try_from_proto(self.node(), &decode_ctx)
1134            }
1135            PhysicalPlanType::Window(_) => {
1136                WindowAggExec::try_from_proto(self.node(), &decode_ctx)
1137            }
1138            PhysicalPlanType::Aggregate(_) => {
1139                AggregateExec::try_from_proto(self.node(), &decode_ctx)
1140            }
1141            PhysicalPlanType::HashJoin(_) => {
1142                HashJoinExec::try_from_proto(self.node(), &decode_ctx)
1143            }
1144            PhysicalPlanType::SymmetricHashJoin(_) => {
1145                SymmetricHashJoinExec::try_from_proto(self.node(), &decode_ctx)
1146            }
1147            PhysicalPlanType::Union(_) => {
1148                UnionExec::try_from_proto(self.node(), &decode_ctx)
1149            }
1150            PhysicalPlanType::Interleave(_) => {
1151                InterleaveExec::try_from_proto(self.node(), &decode_ctx)
1152            }
1153            PhysicalPlanType::CrossJoin(_) => {
1154                CrossJoinExec::try_from_proto(self.node(), &decode_ctx)
1155            }
1156            PhysicalPlanType::Empty(_) => {
1157                EmptyExec::try_from_proto(self.node(), &decode_ctx)
1158            }
1159            PhysicalPlanType::PlaceholderRow(_) => {
1160                PlaceholderRowExec::try_from_proto(self.node(), &decode_ctx)
1161            }
1162            PhysicalPlanType::Sort(_) => {
1163                SortExec::try_from_proto(self.node(), &decode_ctx)
1164            }
1165            PhysicalPlanType::SortPreservingMerge(_) => {
1166                SortPreservingMergeExec::try_from_proto(self.node(), &decode_ctx)
1167            }
1168            PhysicalPlanType::Extension(extension) => {
1169                self.try_into_extension_physical_plan(extension, ctx, proto_converter)
1170            }
1171            PhysicalPlanType::NestedLoopJoin(_) => {
1172                NestedLoopJoinExec::try_from_proto(self.node(), &decode_ctx)
1173            }
1174            PhysicalPlanType::Analyze(_) => {
1175                AnalyzeExec::try_from_proto(self.node(), &decode_ctx)
1176            }
1177            PhysicalPlanType::JsonSink(_) => {
1178                JsonSink::try_from_proto(self.node(), &decode_ctx)
1179            }
1180            PhysicalPlanType::CsvSink(_) => {
1181                CsvSink::try_from_proto(self.node(), &decode_ctx)
1182            }
1183            PhysicalPlanType::ParquetSink(_) => {
1184                #[cfg(feature = "parquet")]
1185                {
1186                    ParquetSink::try_from_proto(self.node(), &decode_ctx)
1187                }
1188                #[cfg(not(feature = "parquet"))]
1189                not_impl_err!("ParquetSink requires the `parquet` feature")
1190            }
1191            PhysicalPlanType::Unnest(_) => {
1192                UnnestExec::try_from_proto(self.node(), &decode_ctx)
1193            }
1194            PhysicalPlanType::Cooperative(_) => {
1195                CooperativeExec::try_from_proto(self.node(), &decode_ctx)
1196            }
1197            PhysicalPlanType::GenerateSeries(generate_series) => {
1198                self.try_into_generate_series_physical_plan(generate_series)
1199            }
1200            PhysicalPlanType::SortMergeJoin(_) => {
1201                SortMergeJoinExec::try_from_proto(self.node(), &decode_ctx)
1202            }
1203            PhysicalPlanType::AsyncFunc(_) => {
1204                AsyncFuncExec::try_from_proto(self.node(), &decode_ctx)
1205            }
1206            PhysicalPlanType::Buffer(_) => {
1207                BufferExec::try_from_proto(self.node(), &decode_ctx)
1208            }
1209            PhysicalPlanType::ScalarSubquery(_) => {
1210                ScalarSubqueryExec::try_from_proto(self.node(), &decode_ctx)
1211            }
1212        }
1213    }
1214
1215    fn try_from_physical_plan_with_converter(
1216        plan: Arc<dyn ExecutionPlan>,
1217        codec: &dyn PhysicalExtensionCodec,
1218        proto_converter: &dyn PhysicalProtoConverterExtension,
1219    ) -> Result<protobuf::PhysicalPlanNode> {
1220        let plan_clone = Arc::clone(&plan);
1221        let mut plan = plan.as_ref();
1222        // Resolve the downcast identity first so wrapper plans serialize as
1223        // their delegate, matching how the `downcast_ref` chain below sees
1224        // them. Without this a wrapper around a migrated plan would hit the
1225        // wrapper's default `try_to_proto` (`Ok(None)`) and find no fallback
1226        // arm for the delegate.
1227        while let Some(delegate) = plan.downcast_delegate() {
1228            plan = delegate;
1229        }
1230
1231        // Self-serializing plans handle themselves via the `try_to_proto` hook
1232        // (#22419). `Ok(None)` means "not migrated" and falls through to the
1233        // central downcast chain below.
1234        let encoder = ConverterPlanEncoder {
1235            codec,
1236            proto_converter,
1237        };
1238        let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
1239        if let Some(node) = plan.try_to_proto(&encode_ctx)? {
1240            return Ok(node);
1241        }
1242
1243        if let Some(exec) = plan.downcast_ref::<LazyMemoryExec>()
1244            && let Some(node) =
1245                protobuf::PhysicalPlanNode::try_from_lazy_memory_exec(exec)?
1246        {
1247            return Ok(node);
1248        }
1249
1250        let mut buf: Vec<u8> = vec![];
1251        match codec.try_encode(Arc::clone(&plan_clone), &mut buf, proto_converter) {
1252            Ok(_) => {
1253                let inputs: Vec<protobuf::PhysicalPlanNode> = plan_clone
1254                    .children()
1255                    .into_iter()
1256                    .cloned()
1257                    .map(|i| {
1258                        protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
1259                            i,
1260                            codec,
1261                            proto_converter,
1262                        )
1263                    })
1264                    .collect::<Result<_>>()?;
1265
1266                Ok(protobuf::PhysicalPlanNode {
1267                    physical_plan_type: Some(PhysicalPlanType::Extension(
1268                        protobuf::PhysicalExtensionNode { node: buf, inputs },
1269                    )),
1270                })
1271            }
1272            Err(e) => internal_err!(
1273                "Unsupported plan and extension codec failed with [{e}]. Plan: {plan_clone:?}"
1274            ),
1275        }
1276    }
1277
1278    fn try_into_extension_physical_plan(
1279        &self,
1280        extension: &protobuf::PhysicalExtensionNode,
1281        ctx: &PhysicalPlanDecodeContext<'_>,
1282        proto_converter: &dyn PhysicalProtoConverterExtension,
1283    ) -> Result<Arc<dyn ExecutionPlan>> {
1284        let inputs: Vec<Arc<dyn ExecutionPlan>> = extension
1285            .inputs
1286            .iter()
1287            .map(|i| proto_converter.proto_to_execution_plan(i, ctx))
1288            .collect::<Result<_>>()?;
1289
1290        let extension_node = ctx.codec().try_decode(
1291            extension.node.as_slice(),
1292            &inputs,
1293            ctx.task_ctx(),
1294            proto_converter,
1295        )?;
1296
1297        Ok(extension_node)
1298    }
1299
1300    fn generate_series_name_to_str(name: protobuf::GenerateSeriesName) -> &'static str {
1301        match name {
1302            protobuf::GenerateSeriesName::GsGenerateSeries => "generate_series",
1303            protobuf::GenerateSeriesName::GsRange => "range",
1304        }
1305    }
1306
1307    fn try_into_generate_series_physical_plan(
1308        &self,
1309        generate_series: &protobuf::GenerateSeriesNode,
1310    ) -> Result<Arc<dyn ExecutionPlan>> {
1311        let schema: SchemaRef = Arc::new(convert_required!(generate_series.schema)?);
1312
1313        let args = match &generate_series.args {
1314            Some(protobuf::generate_series_node::Args::ContainsNull(args)) => {
1315                GenSeriesArgs::ContainsNull {
1316                    name: protobuf::PhysicalPlanNode::generate_series_name_to_str(
1317                        args.name(),
1318                    ),
1319                }
1320            }
1321            Some(protobuf::generate_series_node::Args::Int64Args(args)) => {
1322                GenSeriesArgs::Int64Args {
1323                    start: args.start,
1324                    end: args.end,
1325                    step: args.step,
1326                    include_end: args.include_end,
1327                    name: protobuf::PhysicalPlanNode::generate_series_name_to_str(
1328                        args.name(),
1329                    ),
1330                }
1331            }
1332            Some(protobuf::generate_series_node::Args::TimestampArgs(args)) => {
1333                let step_proto = args.step.as_ref().ok_or_else(|| {
1334                    internal_datafusion_err!("Missing step in TimestampArgs")
1335                })?;
1336                let step = IntervalMonthDayNanoType::make_value(
1337                    step_proto.months,
1338                    step_proto.days,
1339                    step_proto.nanos,
1340                );
1341                GenSeriesArgs::TimestampArgs {
1342                    start: args.start,
1343                    end: args.end,
1344                    step,
1345                    tz: args.tz.as_ref().map(|s| Arc::from(s.as_str())),
1346                    include_end: args.include_end,
1347                    name: protobuf::PhysicalPlanNode::generate_series_name_to_str(
1348                        args.name(),
1349                    ),
1350                }
1351            }
1352            Some(protobuf::generate_series_node::Args::DateArgs(args)) => {
1353                let step_proto = args.step.as_ref().ok_or_else(|| {
1354                    internal_datafusion_err!("Missing step in DateArgs")
1355                })?;
1356                let step = IntervalMonthDayNanoType::make_value(
1357                    step_proto.months,
1358                    step_proto.days,
1359                    step_proto.nanos,
1360                );
1361                GenSeriesArgs::DateArgs {
1362                    start: args.start,
1363                    end: args.end,
1364                    step,
1365                    include_end: args.include_end,
1366                    name: protobuf::PhysicalPlanNode::generate_series_name_to_str(
1367                        args.name(),
1368                    ),
1369                }
1370            }
1371            None => return internal_err!("Missing args in GenerateSeriesNode"),
1372        };
1373
1374        let table = GenerateSeriesTable::new(Arc::clone(&schema), args);
1375        let generator = table.as_generator(generate_series.target_batch_size as usize)?;
1376
1377        Ok(Arc::new(LazyMemoryExec::try_new(schema, vec![generator])?))
1378    }
1379
1380    fn str_to_generate_series_name(name: &str) -> Result<protobuf::GenerateSeriesName> {
1381        match name {
1382            "generate_series" => Ok(protobuf::GenerateSeriesName::GsGenerateSeries),
1383            "range" => Ok(protobuf::GenerateSeriesName::GsRange),
1384            _ => internal_err!("unknown name: {name}"),
1385        }
1386    }
1387
1388    fn try_from_lazy_memory_exec(
1389        exec: &LazyMemoryExec,
1390    ) -> Result<Option<protobuf::PhysicalPlanNode>> {
1391        let generators = exec.generators();
1392
1393        // ensure we only have one generator
1394        let [generator] = generators.as_slice() else {
1395            return Ok(None);
1396        };
1397
1398        let generator_guard = generator.read();
1399
1400        // Try to downcast to different generate_series types
1401        if let Some(empty_gen) = generator_guard.as_any().downcast_ref::<Empty>() {
1402            let schema = exec.schema();
1403            let node = protobuf::GenerateSeriesNode {
1404                schema: Some(schema.as_ref().try_into()?),
1405                target_batch_size: 8192, // Default batch size
1406                args: Some(protobuf::generate_series_node::Args::ContainsNull(
1407                    protobuf::GenerateSeriesArgsContainsNull {
1408                        name: protobuf::PhysicalPlanNode::str_to_generate_series_name(
1409                            empty_gen.name(),
1410                        )? as i32,
1411                    },
1412                )),
1413            };
1414
1415            return Ok(Some(protobuf::PhysicalPlanNode {
1416                physical_plan_type: Some(PhysicalPlanType::GenerateSeries(node)),
1417            }));
1418        }
1419
1420        if let Some(int_64) = generator_guard
1421            .as_any()
1422            .downcast_ref::<GenericSeriesState<i64>>()
1423        {
1424            let schema = exec.schema();
1425            let node = protobuf::GenerateSeriesNode {
1426                schema: Some(schema.as_ref().try_into()?),
1427                target_batch_size: int_64.batch_size() as u32,
1428                args: Some(protobuf::generate_series_node::Args::Int64Args(
1429                    protobuf::GenerateSeriesArgsInt64 {
1430                        start: *int_64.start(),
1431                        end: *int_64.end(),
1432                        step: *int_64.step(),
1433                        include_end: int_64.include_end(),
1434                        name: protobuf::PhysicalPlanNode::str_to_generate_series_name(
1435                            int_64.name(),
1436                        )? as i32,
1437                    },
1438                )),
1439            };
1440
1441            return Ok(Some(protobuf::PhysicalPlanNode {
1442                physical_plan_type: Some(PhysicalPlanType::GenerateSeries(node)),
1443            }));
1444        }
1445
1446        if let Some(timestamp_args) = generator_guard
1447            .as_any()
1448            .downcast_ref::<GenericSeriesState<TimestampValue>>()
1449        {
1450            let schema = exec.schema();
1451
1452            let start = timestamp_args.start().value();
1453            let end = timestamp_args.end().value();
1454
1455            let step_value = timestamp_args.step();
1456
1457            let step = Some(datafusion_proto_common::IntervalMonthDayNanoValue {
1458                months: step_value.months,
1459                days: step_value.days,
1460                nanos: step_value.nanoseconds,
1461            });
1462            let include_end = timestamp_args.include_end();
1463            let name = protobuf::PhysicalPlanNode::str_to_generate_series_name(
1464                timestamp_args.name(),
1465            )? as i32;
1466
1467            let args = match timestamp_args.current().tz_str() {
1468                Some(tz) => protobuf::generate_series_node::Args::TimestampArgs(
1469                    protobuf::GenerateSeriesArgsTimestamp {
1470                        start,
1471                        end,
1472                        step,
1473                        include_end,
1474                        name,
1475                        tz: Some(tz.to_string()),
1476                    },
1477                ),
1478                None => protobuf::generate_series_node::Args::DateArgs(
1479                    protobuf::GenerateSeriesArgsDate {
1480                        start,
1481                        end,
1482                        step,
1483                        include_end,
1484                        name,
1485                    },
1486                ),
1487            };
1488
1489            let node = protobuf::GenerateSeriesNode {
1490                schema: Some(schema.as_ref().try_into()?),
1491                target_batch_size: timestamp_args.batch_size() as u32,
1492                args: Some(args),
1493            };
1494
1495            return Ok(Some(protobuf::PhysicalPlanNode {
1496                physical_plan_type: Some(PhysicalPlanType::GenerateSeries(node)),
1497            }));
1498        }
1499
1500        Ok(None)
1501    }
1502}
1503
1504impl PhysicalPlanNodeExt for protobuf::PhysicalPlanNode {
1505    fn node(&self) -> &protobuf::PhysicalPlanNode {
1506        self
1507    }
1508}
1509
1510pub trait AsExecutionPlan: Debug + Send + Sync + Clone {
1511    fn try_decode(buf: &[u8]) -> Result<Self>
1512    where
1513        Self: Sized;
1514
1515    fn try_encode<B>(&self, buf: &mut B) -> Result<()>
1516    where
1517        B: BufMut,
1518        Self: Sized;
1519
1520    fn try_into_physical_plan(
1521        &self,
1522        ctx: &TaskContext,
1523
1524        codec: &dyn PhysicalExtensionCodec,
1525    ) -> Result<Arc<dyn ExecutionPlan>>;
1526
1527    fn try_from_physical_plan(
1528        plan: Arc<dyn ExecutionPlan>,
1529        codec: &dyn PhysicalExtensionCodec,
1530    ) -> Result<Self>
1531    where
1532        Self: Sized;
1533}
1534
1535pub trait PhysicalExtensionCodec: Debug + Send + Sync + Any {
1536    fn try_decode(
1537        &self,
1538        buf: &[u8],
1539        inputs: &[Arc<dyn ExecutionPlan>],
1540        ctx: &TaskContext,
1541        proto_converter: &dyn PhysicalProtoConverterExtension,
1542    ) -> Result<Arc<dyn ExecutionPlan>>;
1543
1544    fn try_encode(
1545        &self,
1546        node: Arc<dyn ExecutionPlan>,
1547        buf: &mut Vec<u8>,
1548        proto_converter: &dyn PhysicalProtoConverterExtension,
1549    ) -> Result<()>;
1550
1551    fn try_decode_udf(&self, name: &str, _buf: &[u8]) -> Result<Arc<ScalarUDF>> {
1552        not_impl_err!("PhysicalExtensionCodec is not provided for scalar function {name}")
1553    }
1554
1555    fn try_encode_udf(&self, _node: &ScalarUDF, _buf: &mut Vec<u8>) -> Result<()> {
1556        Ok(())
1557    }
1558
1559    fn try_decode_higher_order_function(
1560        &self,
1561        name: &str,
1562        _buf: &[u8],
1563    ) -> Result<Arc<HigherOrderUDF>> {
1564        not_impl_err!(
1565            "PhysicalExtensionCodec is not provided for higher order function {name}"
1566        )
1567    }
1568
1569    fn try_encode_higher_order_function(
1570        &self,
1571        _node: &HigherOrderUDF,
1572        _buf: &mut Vec<u8>,
1573    ) -> Result<()> {
1574        Ok(())
1575    }
1576
1577    /// Decode a custom extension expression from `buf`.
1578    ///
1579    /// `inputs` holds the already-decoded children carried in the
1580    /// `PhysicalExtensionExprNode.inputs` field. If the codec instead embeds
1581    /// nested `PhysicalExprNode`s *inside* `buf`, decode them through
1582    /// `ctx.decode(..)` (equivalently [`PhysicalExprDecodeCtx::decode`]) rather
1583    /// than the free [`parse_physical_expr`] function: `ctx` carries the active
1584    /// schema and task context (so UDF/column references resolve against the
1585    /// real registry) and routes through any active `DeduplicatingDeserializer`,
1586    /// so a shared inner expression (e.g. a `DynamicFilterPhysicalExpr`
1587    /// referenced both from a `SortExec.filter` and from inside this blob)
1588    /// cache-hits on its `expr_id` and re-shares one `Arc<dyn PhysicalExpr>`.
1589    ///
1590    /// [`parse_physical_expr`]: crate::physical_plan::from_proto::parse_physical_expr
1591    fn try_decode_expr(
1592        &self,
1593        _buf: &[u8],
1594        _inputs: &[Arc<dyn PhysicalExpr>],
1595        _ctx: &PhysicalExprDecodeCtx<'_>,
1596    ) -> Result<Arc<dyn PhysicalExpr>> {
1597        not_impl_err!("PhysicalExtensionCodec is not provided")
1598    }
1599
1600    /// Encode a custom extension expression into `buf`.
1601    ///
1602    /// If the codec embeds nested `PhysicalExprNode`s inside `buf`, encode them
1603    /// through `ctx.encode_child(..)` (equivalently
1604    /// [`PhysicalExprEncodeCtx::encode_child`]) rather than the free
1605    /// [`serialize_physical_expr`] function, so an active
1606    /// `DeduplicatingProtoConverter` stamps matching `expr_id`s for shared
1607    /// inner expressions. See [`Self::try_decode_expr`].
1608    ///
1609    /// [`serialize_physical_expr`]: crate::physical_plan::to_proto::serialize_physical_expr
1610    fn try_encode_expr(
1611        &self,
1612        _node: &Arc<dyn PhysicalExpr>,
1613        _buf: &mut Vec<u8>,
1614        _ctx: &PhysicalExprEncodeCtx<'_>,
1615    ) -> Result<()> {
1616        not_impl_err!("PhysicalExtensionCodec is not provided")
1617    }
1618
1619    fn try_decode_udaf(&self, name: &str, _buf: &[u8]) -> Result<Arc<AggregateUDF>> {
1620        not_impl_err!(
1621            "PhysicalExtensionCodec is not provided for aggregate function {name}"
1622        )
1623    }
1624
1625    fn try_encode_udaf(&self, _node: &AggregateUDF, _buf: &mut Vec<u8>) -> Result<()> {
1626        Ok(())
1627    }
1628
1629    fn try_decode_udwf(&self, name: &str, _buf: &[u8]) -> Result<Arc<WindowUDF>> {
1630        not_impl_err!("PhysicalExtensionCodec is not provided for window function {name}")
1631    }
1632
1633    fn try_encode_udwf(&self, _node: &WindowUDF, _buf: &mut Vec<u8>) -> Result<()> {
1634        Ok(())
1635    }
1636}
1637
1638#[derive(Debug)]
1639pub struct DefaultPhysicalExtensionCodec {}
1640
1641impl PhysicalExtensionCodec for DefaultPhysicalExtensionCodec {
1642    fn try_decode(
1643        &self,
1644        _buf: &[u8],
1645        _inputs: &[Arc<dyn ExecutionPlan>],
1646        _ctx: &TaskContext,
1647        _proto_converter: &dyn PhysicalProtoConverterExtension,
1648    ) -> Result<Arc<dyn ExecutionPlan>> {
1649        not_impl_err!("PhysicalExtensionCodec is not provided")
1650    }
1651
1652    fn try_encode(
1653        &self,
1654        _node: Arc<dyn ExecutionPlan>,
1655        _buf: &mut Vec<u8>,
1656        _proto_converter: &dyn PhysicalProtoConverterExtension,
1657    ) -> Result<()> {
1658        not_impl_err!("PhysicalExtensionCodec is not provided")
1659    }
1660}
1661
1662/// Controls the conversion of physical plans and expressions to and from their
1663/// Protobuf variants. Using this trait, users can perform optimizations on the
1664/// conversion process or collect performance metrics.
1665pub trait PhysicalProtoConverterExtension {
1666    fn proto_to_execution_plan(
1667        &self,
1668        proto: &protobuf::PhysicalPlanNode,
1669        ctx: &PhysicalPlanDecodeContext<'_>,
1670    ) -> Result<Arc<dyn ExecutionPlan>>;
1671
1672    fn default_proto_to_execution_plan(
1673        &self,
1674        proto: &protobuf::PhysicalPlanNode,
1675        ctx: &PhysicalPlanDecodeContext<'_>,
1676    ) -> Result<Arc<dyn ExecutionPlan>>
1677    where
1678        Self: Sized,
1679    {
1680        proto.try_into_physical_plan_with_context(ctx, self)
1681    }
1682
1683    fn execution_plan_to_proto(
1684        &self,
1685        plan: &Arc<dyn ExecutionPlan>,
1686        codec: &dyn PhysicalExtensionCodec,
1687    ) -> Result<protobuf::PhysicalPlanNode>;
1688
1689    fn proto_to_physical_expr(
1690        &self,
1691        proto: &protobuf::PhysicalExprNode,
1692        input_schema: &Schema,
1693        ctx: &PhysicalPlanDecodeContext<'_>,
1694    ) -> Result<Arc<dyn PhysicalExpr>>;
1695
1696    fn default_proto_to_physical_expr(
1697        &self,
1698        proto: &protobuf::PhysicalExprNode,
1699        input_schema: &Schema,
1700        ctx: &PhysicalPlanDecodeContext<'_>,
1701    ) -> Result<Arc<dyn PhysicalExpr>>
1702    where
1703        Self: Sized,
1704    {
1705        parse_physical_expr_with_converter(proto, input_schema, ctx, self)
1706    }
1707
1708    fn physical_expr_to_proto(
1709        &self,
1710        expr: &Arc<dyn PhysicalExpr>,
1711        codec: &dyn PhysicalExtensionCodec,
1712    ) -> Result<protobuf::PhysicalExprNode>;
1713}
1714
1715/// DataEncoderTuple captures the position of the encoder
1716/// in the codec list that was used to encode the data and actual encoded data
1717#[derive(Clone, PartialEq, prost::Message)]
1718struct DataEncoderTuple {
1719    /// The position of encoder used to encode data
1720    /// (to be used for decoding)
1721    #[prost(uint32, tag = 1)]
1722    pub encoder_position: u32,
1723
1724    #[prost(bytes, tag = 2)]
1725    pub blob: Vec<u8>,
1726}
1727
1728pub struct DefaultPhysicalProtoConverter {}
1729
1730impl PhysicalProtoConverterExtension for DefaultPhysicalProtoConverter {
1731    fn proto_to_execution_plan(
1732        &self,
1733        proto: &protobuf::PhysicalPlanNode,
1734        ctx: &PhysicalPlanDecodeContext<'_>,
1735    ) -> Result<Arc<dyn ExecutionPlan>> {
1736        proto.try_into_physical_plan_with_context(ctx, self)
1737    }
1738
1739    fn execution_plan_to_proto(
1740        &self,
1741        plan: &Arc<dyn ExecutionPlan>,
1742        codec: &dyn PhysicalExtensionCodec,
1743    ) -> Result<protobuf::PhysicalPlanNode>
1744    where
1745        Self: Sized,
1746    {
1747        protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
1748            Arc::clone(plan),
1749            codec,
1750            self,
1751        )
1752    }
1753
1754    fn proto_to_physical_expr(
1755        &self,
1756        proto: &protobuf::PhysicalExprNode,
1757        input_schema: &Schema,
1758        ctx: &PhysicalPlanDecodeContext<'_>,
1759    ) -> Result<Arc<dyn PhysicalExpr>>
1760    where
1761        Self: Sized,
1762    {
1763        // Default implementation calls the free function
1764        parse_physical_expr_with_converter(proto, input_schema, ctx, self)
1765    }
1766
1767    fn physical_expr_to_proto(
1768        &self,
1769        expr: &Arc<dyn PhysicalExpr>,
1770        codec: &dyn PhysicalExtensionCodec,
1771    ) -> Result<protobuf::PhysicalExprNode> {
1772        serialize_physical_expr_with_converter(expr, codec, self)
1773    }
1774}
1775
1776/// Internal deserializer that caches expressions by their `expression_id()` so
1777/// multiple occurrences of the same expression are deduped.
1778#[derive(Default)]
1779struct DeduplicatingDeserializer {
1780    /// Cache mapping expression_id to deserialized expressions.
1781    cache: RefCell<HashMap<u64, Arc<dyn PhysicalExpr>>>,
1782}
1783
1784impl PhysicalProtoConverterExtension for DeduplicatingDeserializer {
1785    fn proto_to_execution_plan(
1786        &self,
1787        proto: &protobuf::PhysicalPlanNode,
1788        ctx: &PhysicalPlanDecodeContext<'_>,
1789    ) -> Result<Arc<dyn ExecutionPlan>> {
1790        proto.try_into_physical_plan_with_context(ctx, self)
1791    }
1792
1793    fn execution_plan_to_proto(
1794        &self,
1795        _plan: &Arc<dyn ExecutionPlan>,
1796        _codec: &dyn PhysicalExtensionCodec,
1797    ) -> Result<protobuf::PhysicalPlanNode>
1798    where
1799        Self: Sized,
1800    {
1801        internal_err!("DeduplicatingDeserializer cannot serialize execution plans")
1802    }
1803
1804    fn proto_to_physical_expr(
1805        &self,
1806        proto: &protobuf::PhysicalExprNode,
1807        input_schema: &Schema,
1808        ctx: &PhysicalPlanDecodeContext<'_>,
1809    ) -> Result<Arc<dyn PhysicalExpr>>
1810    where
1811        Self: Sized,
1812    {
1813        // `expr_id` is the generic identity slot on `PhysicalExprNode`.
1814        // The default serializer populates it from `PhysicalExpr::expression_id`.
1815        // A missing id means this expression type doesn't participate in deduping.
1816        let Some(id) = proto.expr_id else {
1817            return parse_physical_expr_with_converter(proto, input_schema, ctx, self);
1818        };
1819
1820        let parsed = parse_physical_expr_with_converter(proto, input_schema, ctx, self)?;
1821
1822        let mut cache = self.cache.borrow_mut();
1823        if let Some(cached) = cache.get(&id) {
1824            // Since expressions may manage their own internal state when deriving
1825            // expressions via `with_new_children`, we use `with_new_children`
1826            // to opt into the same behavior.
1827            //
1828            // For example, one `DynamicFilterPhysicalExpr` may be derived from
1829            // another resulting in shared references. Using `with_new_children`
1830            // is meant to preserve those references.
1831            let children: Vec<_> = parsed.children().into_iter().cloned().collect();
1832            return Arc::clone(cached).with_new_children(children);
1833        }
1834
1835        cache.insert(id, Arc::clone(&parsed));
1836        Ok(parsed)
1837    }
1838
1839    fn physical_expr_to_proto(
1840        &self,
1841        _expr: &Arc<dyn PhysicalExpr>,
1842        _codec: &dyn PhysicalExtensionCodec,
1843    ) -> Result<protobuf::PhysicalExprNode> {
1844        internal_err!("DeduplicatingDeserializer cannot serialize physical expressions")
1845    }
1846}
1847
1848/// A proto converter that deduplicates [`PhysicalExpr`] by [`PhysicalExpr::expression_id`].
1849/// This helps preserve referential integrity when deserializing [`ExecutionPlan`]s
1850/// which may contain multiple occurrences of the same [`PhysicalExpr`] (ex. when
1851/// [`DynamicFilterPhysicalExpr`] are pushed down, it is important to preserve
1852/// referential integrity).
1853///
1854///
1855/// [`DynamicFilterPhysicalExpr`]: https://docs.rs/datafusion-physical-expr/latest/datafusion_physical_expr/expressions/struct.DynamicFilterPhysicalExpr.html
1856#[derive(Debug, Default, Clone, Copy)]
1857pub struct DeduplicatingProtoConverter {}
1858
1859impl PhysicalProtoConverterExtension for DeduplicatingProtoConverter {
1860    fn proto_to_execution_plan(
1861        &self,
1862        proto: &protobuf::PhysicalPlanNode,
1863        ctx: &PhysicalPlanDecodeContext<'_>,
1864    ) -> Result<Arc<dyn ExecutionPlan>> {
1865        let deserializer = DeduplicatingDeserializer::default();
1866        proto.try_into_physical_plan_with_context(ctx, &deserializer)
1867    }
1868
1869    fn execution_plan_to_proto(
1870        &self,
1871        plan: &Arc<dyn ExecutionPlan>,
1872        codec: &dyn PhysicalExtensionCodec,
1873    ) -> Result<protobuf::PhysicalPlanNode>
1874    where
1875        Self: Sized,
1876    {
1877        protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
1878            Arc::clone(plan),
1879            codec,
1880            self,
1881        )
1882    }
1883
1884    fn proto_to_physical_expr(
1885        &self,
1886        proto: &protobuf::PhysicalExprNode,
1887        input_schema: &Schema,
1888        ctx: &PhysicalPlanDecodeContext<'_>,
1889    ) -> Result<Arc<dyn PhysicalExpr>>
1890    where
1891        Self: Sized,
1892    {
1893        let deserializer = DeduplicatingDeserializer::default();
1894        deserializer.proto_to_physical_expr(proto, input_schema, ctx)
1895    }
1896
1897    fn physical_expr_to_proto(
1898        &self,
1899        expr: &Arc<dyn PhysicalExpr>,
1900        codec: &dyn PhysicalExtensionCodec,
1901    ) -> Result<protobuf::PhysicalExprNode> {
1902        serialize_physical_expr_with_converter(expr, codec, self)
1903    }
1904}
1905
1906/// A PhysicalExtensionCodec that tries one of multiple inner codecs
1907/// until one works
1908#[derive(Debug)]
1909pub struct ComposedPhysicalExtensionCodec {
1910    codecs: Vec<Arc<dyn PhysicalExtensionCodec>>,
1911}
1912
1913impl ComposedPhysicalExtensionCodec {
1914    // Position in this codecs list is important as it will be used for decoding.
1915    // If new codec is added it should go to last position.
1916    pub fn new(codecs: Vec<Arc<dyn PhysicalExtensionCodec>>) -> Self {
1917        Self { codecs }
1918    }
1919
1920    fn decode_protobuf<R>(
1921        &self,
1922        buf: &[u8],
1923        decode: impl FnOnce(&dyn PhysicalExtensionCodec, &[u8]) -> Result<R>,
1924    ) -> Result<R> {
1925        let proto =
1926            DataEncoderTuple::decode(buf).map_err(|e| internal_datafusion_err!("{e}"))?;
1927
1928        let codec = self.codecs.get(proto.encoder_position as usize).ok_or(
1929            internal_datafusion_err!("Can't find required codec in codec list"),
1930        )?;
1931
1932        decode(codec.as_ref(), &proto.blob)
1933    }
1934
1935    fn encode_protobuf(
1936        &self,
1937        buf: &mut Vec<u8>,
1938        mut encode: impl FnMut(&dyn PhysicalExtensionCodec, &mut Vec<u8>) -> Result<()>,
1939    ) -> Result<()> {
1940        let mut data = vec![];
1941        let mut last_err = None;
1942        let mut encoder_position = None;
1943
1944        // find the encoder
1945        for (position, codec) in self.codecs.iter().enumerate() {
1946            match encode(codec.as_ref(), &mut data) {
1947                Ok(_) => {
1948                    encoder_position = Some(position as u32);
1949                    break;
1950                }
1951                Err(err) => last_err = Some(err),
1952            }
1953        }
1954
1955        let encoder_position = encoder_position.ok_or_else(|| {
1956            last_err.unwrap_or_else(|| {
1957                DataFusionError::NotImplemented(
1958                    "Empty list of composed codecs".to_owned(),
1959                )
1960            })
1961        })?;
1962
1963        // encode with encoder position
1964        let proto = DataEncoderTuple {
1965            encoder_position,
1966            blob: data,
1967        };
1968        proto
1969            .encode(buf)
1970            .map_err(|e| internal_datafusion_err!("{e}"))
1971    }
1972}
1973
1974impl PhysicalExtensionCodec for ComposedPhysicalExtensionCodec {
1975    fn try_decode(
1976        &self,
1977        buf: &[u8],
1978        inputs: &[Arc<dyn ExecutionPlan>],
1979        ctx: &TaskContext,
1980        proto_converter: &dyn PhysicalProtoConverterExtension,
1981    ) -> Result<Arc<dyn ExecutionPlan>> {
1982        self.decode_protobuf(buf, |codec, data| {
1983            codec.try_decode(data, inputs, ctx, proto_converter)
1984        })
1985    }
1986
1987    fn try_encode(
1988        &self,
1989        node: Arc<dyn ExecutionPlan>,
1990        buf: &mut Vec<u8>,
1991        proto_converter: &dyn PhysicalProtoConverterExtension,
1992    ) -> Result<()> {
1993        self.encode_protobuf(buf, |codec, data| {
1994            codec.try_encode(Arc::clone(&node), data, proto_converter)
1995        })
1996    }
1997
1998    fn try_decode_udf(&self, name: &str, buf: &[u8]) -> Result<Arc<ScalarUDF>> {
1999        self.decode_protobuf(buf, |codec, data| codec.try_decode_udf(name, data))
2000    }
2001
2002    fn try_encode_udf(&self, node: &ScalarUDF, buf: &mut Vec<u8>) -> Result<()> {
2003        self.encode_protobuf(buf, |codec, data| codec.try_encode_udf(node, data))
2004    }
2005
2006    fn try_decode_udaf(&self, name: &str, buf: &[u8]) -> Result<Arc<AggregateUDF>> {
2007        self.decode_protobuf(buf, |codec, data| codec.try_decode_udaf(name, data))
2008    }
2009
2010    fn try_encode_udaf(&self, node: &AggregateUDF, buf: &mut Vec<u8>) -> Result<()> {
2011        self.encode_protobuf(buf, |codec, data| codec.try_encode_udaf(node, data))
2012    }
2013}
2014
2015/// Adapter backing [`ExecutionPlanEncodeCtx`] for plans migrated to the
2016/// `try_to_proto` hook (#22419). Routes child-plan and child-expr encoding back
2017/// through the central converter so nested plans honor their own hooks.
2018struct ConverterPlanEncoder<'a> {
2019    codec: &'a dyn PhysicalExtensionCodec,
2020    proto_converter: &'a dyn PhysicalProtoConverterExtension,
2021}
2022
2023impl ExecutionPlanEncode for ConverterPlanEncoder<'_> {
2024    fn encode_plan(
2025        &self,
2026        plan: &Arc<dyn ExecutionPlan>,
2027    ) -> Result<protobuf::PhysicalPlanNode> {
2028        self.proto_converter
2029            .execution_plan_to_proto(plan, self.codec)
2030    }
2031
2032    fn encode_expr(
2033        &self,
2034        expr: &Arc<dyn PhysicalExpr>,
2035    ) -> Result<protobuf::PhysicalExprNode> {
2036        self.proto_converter
2037            .physical_expr_to_proto(expr, self.codec)
2038    }
2039
2040    // Bytes-only function serde. `(!buf.is_empty()).then_some(buf)` preserves the
2041    // existing `fun_definition` wire semantics (empty payload == encode-by-name).
2042    fn encode_udf(&self, udf: &ScalarUDF) -> Result<Option<Vec<u8>>> {
2043        let mut buf = vec![];
2044        self.codec.try_encode_udf(udf, &mut buf)?;
2045        Ok((!buf.is_empty()).then_some(buf))
2046    }
2047
2048    fn encode_udaf(&self, udaf: &AggregateUDF) -> Result<Option<Vec<u8>>> {
2049        let mut buf = vec![];
2050        self.codec.try_encode_udaf(udaf, &mut buf)?;
2051        Ok((!buf.is_empty()).then_some(buf))
2052    }
2053
2054    fn encode_udwf(&self, udwf: &WindowUDF) -> Result<Option<Vec<u8>>> {
2055        let mut buf = vec![];
2056        self.codec.try_encode_udwf(udwf, &mut buf)?;
2057        Ok((!buf.is_empty()).then_some(buf))
2058    }
2059}
2060
2061/// Adapter backing [`ExecutionPlanDecodeCtx`] for plans migrated to the
2062/// `try_from_proto` pattern (#22419). Routes child-plan and child-expr decoding
2063/// back through the central converter, and exposes the session task context
2064/// (never the extension codec).
2065struct ConverterPlanDecoder<'a, 'ctx> {
2066    ctx: &'a PhysicalPlanDecodeContext<'ctx>,
2067    proto_converter: &'a dyn PhysicalProtoConverterExtension,
2068}
2069
2070impl ExecutionPlanDecode for ConverterPlanDecoder<'_, '_> {
2071    fn decode_plan(
2072        &self,
2073        node: &protobuf::PhysicalPlanNode,
2074    ) -> Result<Arc<dyn ExecutionPlan>> {
2075        self.proto_converter.proto_to_execution_plan(node, self.ctx)
2076    }
2077
2078    fn decode_plan_with_scalar_subquery_results(
2079        &self,
2080        node: &protobuf::PhysicalPlanNode,
2081        results: ScalarSubqueryResults,
2082    ) -> Result<Arc<dyn ExecutionPlan>> {
2083        let scoped_ctx = self.ctx.with_scalar_subquery_results(results);
2084        self.proto_converter
2085            .proto_to_execution_plan(node, &scoped_ctx)
2086    }
2087
2088    fn decode_expr(
2089        &self,
2090        node: &protobuf::PhysicalExprNode,
2091        input_schema: &Schema,
2092    ) -> Result<Arc<dyn PhysicalExpr>> {
2093        self.proto_converter
2094            .proto_to_physical_expr(node, input_schema, self.ctx)
2095    }
2096
2097    fn task_ctx(&self) -> &TaskContext {
2098        self.ctx.task_ctx()
2099    }
2100
2101    // Lookup-order policy, owned here so no plan re-derives it: an explicit
2102    // payload is decoded by the codec; otherwise resolve by name from the
2103    // registry, falling back to the codec with an empty buffer.
2104    fn decode_udf(&self, name: &str, payload: Option<&[u8]>) -> Result<Arc<ScalarUDF>> {
2105        match payload {
2106            Some(buf) => self.ctx.codec().try_decode_udf(name, buf),
2107            None => self
2108                .ctx
2109                .task_ctx()
2110                .udf(name)
2111                .or_else(|_| self.ctx.codec().try_decode_udf(name, &[])),
2112        }
2113    }
2114
2115    fn decode_udaf(
2116        &self,
2117        name: &str,
2118        payload: Option<&[u8]>,
2119    ) -> Result<Arc<AggregateUDF>> {
2120        match payload {
2121            Some(buf) => self.ctx.codec().try_decode_udaf(name, buf),
2122            None => self
2123                .ctx
2124                .task_ctx()
2125                .udaf(name)
2126                .or_else(|_| self.ctx.codec().try_decode_udaf(name, &[])),
2127        }
2128    }
2129
2130    fn decode_udwf(&self, name: &str, payload: Option<&[u8]>) -> Result<Arc<WindowUDF>> {
2131        match payload {
2132            Some(buf) => self.ctx.codec().try_decode_udwf(name, buf),
2133            None => self
2134                .ctx
2135                .task_ctx()
2136                .udwf(name)
2137                .or_else(|_| self.ctx.codec().try_decode_udwf(name, &[])),
2138        }
2139    }
2140}