1use std::sync::Arc;
19
20use crate::scalar_subquery::ScalarSubqueryExpr;
21use crate::{HigherOrderFunctionExpr, ScalarFunctionExpr};
22use crate::{
23 PhysicalExpr,
24 expressions::{self, Column, Literal, binary, like, similar_to},
25};
26
27use arrow::datatypes::Schema;
28use datafusion_common::config::ConfigOptions;
29use datafusion_common::datatype::FieldExt;
30use datafusion_common::metadata::FieldMetadata;
31use datafusion_common::{
32 DFSchema, Result, ScalarValue, TableReference, ToDFSchema, exec_err,
33 internal_datafusion_err, not_impl_err, plan_datafusion_err, plan_err,
34};
35use datafusion_expr::execution_props::ExecutionProps;
36use datafusion_expr::expr::{
37 Alias, Cast, HigherOrderFunction, InList, Lambda, LambdaVariable, Placeholder,
38 ScalarFunction,
39};
40use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
41use datafusion_expr::var_provider::VarType;
42use datafusion_expr::var_provider::is_system_variables;
43use datafusion_expr::{
44 Between, BinaryExpr, Expr, Like, Operator, TryCast, binary_expr, lit,
45};
46
47#[cfg_attr(feature = "recursive_protection", recursive::recursive)]
133pub fn create_physical_expr(
134 e: &Expr,
135 input_dfschema: &DFSchema,
136 execution_props: &ExecutionProps,
137 planning_ctx: &PhysicalPlanningContext,
138) -> Result<Arc<dyn PhysicalExpr>> {
139 let input_schema = input_dfschema.as_arrow();
140
141 match e {
142 Expr::Alias(Alias { expr, metadata, .. }) => {
143 if let Expr::Literal(v, prior_metadata) = expr.as_ref() {
144 let new_metadata = FieldMetadata::merge_options(
145 prior_metadata.as_ref(),
146 metadata.as_ref(),
147 );
148 Ok(Arc::new(Literal::new_with_metadata(
149 v.clone(),
150 new_metadata,
151 )))
152 } else {
153 Ok(create_physical_expr(
154 expr,
155 input_dfschema,
156 execution_props,
157 planning_ctx,
158 )?)
159 }
160 }
161 Expr::Column(c) => {
162 let idx = input_dfschema.index_of_column(c)?;
163 Ok(Arc::new(Column::new(&c.name, idx)))
164 }
165 Expr::Literal(value, metadata) => Ok(Arc::new(Literal::new_with_metadata(
166 value.clone(),
167 metadata.clone(),
168 ))),
169 Expr::ScalarVariable(_, variable_names) => {
170 if is_system_variables(variable_names) {
171 match execution_props.get_var_provider(VarType::System) {
172 Some(provider) => {
173 let scalar_value = provider.get_value(variable_names.clone())?;
174 Ok(Arc::new(Literal::new(scalar_value)))
175 }
176 _ => plan_err!("No system variable provider found"),
177 }
178 } else {
179 match execution_props.get_var_provider(VarType::UserDefined) {
180 Some(provider) => {
181 let scalar_value = provider.get_value(variable_names.clone())?;
182 Ok(Arc::new(Literal::new(scalar_value)))
183 }
184 _ => plan_err!("No user defined variable provider found"),
185 }
186 }
187 }
188 Expr::IsTrue(expr) => {
189 let binary_op = binary_expr(
190 expr.as_ref().clone(),
191 Operator::IsNotDistinctFrom,
192 lit(true),
193 );
194 create_physical_expr(
195 &binary_op,
196 input_dfschema,
197 execution_props,
198 planning_ctx,
199 )
200 }
201 Expr::IsNotTrue(expr) => {
202 let binary_op =
203 binary_expr(expr.as_ref().clone(), Operator::IsDistinctFrom, lit(true));
204 create_physical_expr(
205 &binary_op,
206 input_dfschema,
207 execution_props,
208 planning_ctx,
209 )
210 }
211 Expr::IsFalse(expr) => {
212 let binary_op = binary_expr(
213 expr.as_ref().clone(),
214 Operator::IsNotDistinctFrom,
215 lit(false),
216 );
217 create_physical_expr(
218 &binary_op,
219 input_dfschema,
220 execution_props,
221 planning_ctx,
222 )
223 }
224 Expr::IsNotFalse(expr) => {
225 let binary_op =
226 binary_expr(expr.as_ref().clone(), Operator::IsDistinctFrom, lit(false));
227 create_physical_expr(
228 &binary_op,
229 input_dfschema,
230 execution_props,
231 planning_ctx,
232 )
233 }
234 Expr::IsUnknown(expr) => {
235 let binary_op = binary_expr(
236 expr.as_ref().clone(),
237 Operator::IsNotDistinctFrom,
238 Expr::Literal(ScalarValue::Boolean(None), None),
239 );
240 create_physical_expr(
241 &binary_op,
242 input_dfschema,
243 execution_props,
244 planning_ctx,
245 )
246 }
247 Expr::IsNotUnknown(expr) => {
248 let binary_op = binary_expr(
249 expr.as_ref().clone(),
250 Operator::IsDistinctFrom,
251 Expr::Literal(ScalarValue::Boolean(None), None),
252 );
253 create_physical_expr(
254 &binary_op,
255 input_dfschema,
256 execution_props,
257 planning_ctx,
258 )
259 }
260 Expr::BinaryExpr(BinaryExpr { left, op, right }) => {
261 let lhs = create_physical_expr(
263 left,
264 input_dfschema,
265 execution_props,
266 planning_ctx,
267 )?;
268 let rhs = create_physical_expr(
269 right,
270 input_dfschema,
271 execution_props,
272 planning_ctx,
273 )?;
274 binary(lhs, *op, rhs, input_schema)
282 }
283 Expr::Like(Like {
284 negated,
285 expr,
286 pattern,
287 escape_char,
288 case_insensitive,
289 }) => {
290 if escape_char.unwrap_or('\\') != '\\' {
292 return exec_err!(
293 "LIKE does not support escape_char other than the backslash (\\)"
294 );
295 }
296 let physical_expr = create_physical_expr(
297 expr,
298 input_dfschema,
299 execution_props,
300 planning_ctx,
301 )?;
302 let physical_pattern = create_physical_expr(
303 pattern,
304 input_dfschema,
305 execution_props,
306 planning_ctx,
307 )?;
308 like(
309 *negated,
310 *case_insensitive,
311 physical_expr,
312 physical_pattern,
313 input_schema,
314 )
315 }
316 Expr::SimilarTo(Like {
317 negated,
318 expr,
319 pattern,
320 escape_char,
321 case_insensitive,
322 }) => {
323 if escape_char.is_some() {
324 return exec_err!("SIMILAR TO does not support escape_char yet");
325 }
326 let physical_expr = create_physical_expr(
327 expr,
328 input_dfschema,
329 execution_props,
330 planning_ctx,
331 )?;
332 let physical_pattern = create_physical_expr(
333 pattern,
334 input_dfschema,
335 execution_props,
336 planning_ctx,
337 )?;
338 similar_to(*negated, *case_insensitive, physical_expr, physical_pattern)
339 }
340 Expr::Case(case) => {
341 let expr: Option<Arc<dyn PhysicalExpr>> = if let Some(e) = &case.expr {
342 Some(create_physical_expr(
343 e.as_ref(),
344 input_dfschema,
345 execution_props,
346 planning_ctx,
347 )?)
348 } else {
349 None
350 };
351 let (when_expr, then_expr): (Vec<&Expr>, Vec<&Expr>) = case
352 .when_then_expr
353 .iter()
354 .map(|(w, t)| (w.as_ref(), t.as_ref()))
355 .unzip();
356 let when_expr = create_physical_exprs(
357 when_expr,
358 input_dfschema,
359 execution_props,
360 planning_ctx,
361 )?;
362 let then_expr = create_physical_exprs(
363 then_expr,
364 input_dfschema,
365 execution_props,
366 planning_ctx,
367 )?;
368 let when_then_expr: Vec<(Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>)> =
369 when_expr
370 .iter()
371 .zip(then_expr.iter())
372 .map(|(w, t)| (Arc::clone(w), Arc::clone(t)))
373 .collect();
374 let else_expr: Option<Arc<dyn PhysicalExpr>> =
375 if let Some(e) = &case.else_expr {
376 Some(create_physical_expr(
377 e.as_ref(),
378 input_dfschema,
379 execution_props,
380 planning_ctx,
381 )?)
382 } else {
383 None
384 };
385 Ok(expressions::case(expr, when_then_expr, else_expr)?)
386 }
387 Expr::Cast(Cast { expr, field }) => expressions::cast_with_target_field(
388 create_physical_expr(expr, input_dfschema, execution_props, planning_ctx)?,
389 input_schema,
390 field,
391 None,
392 ),
393 Expr::TryCast(TryCast { expr, field }) => {
394 expressions::try_cast_with_target_field(
395 create_physical_expr(
396 expr,
397 input_dfschema,
398 execution_props,
399 planning_ctx,
400 )?,
401 input_schema,
402 field,
403 )
404 }
405 Expr::Not(expr) => expressions::not(create_physical_expr(
406 expr,
407 input_dfschema,
408 execution_props,
409 planning_ctx,
410 )?),
411 Expr::Negative(expr) => expressions::negative(
412 create_physical_expr(expr, input_dfschema, execution_props, planning_ctx)?,
413 input_schema,
414 ),
415 Expr::IsNull(expr) => expressions::is_null(create_physical_expr(
416 expr,
417 input_dfschema,
418 execution_props,
419 planning_ctx,
420 )?),
421 Expr::IsNotNull(expr) => expressions::is_not_null(create_physical_expr(
422 expr,
423 input_dfschema,
424 execution_props,
425 planning_ctx,
426 )?),
427 Expr::ScalarFunction(ScalarFunction { func, args }) => {
428 let physical_args = create_physical_exprs(
429 args,
430 input_dfschema,
431 execution_props,
432 planning_ctx,
433 )?;
434 let config_options = match execution_props.config_options.as_ref() {
435 Some(config_options) => Arc::clone(config_options),
436 None => Arc::new(ConfigOptions::default()),
437 };
438
439 Ok(Arc::new(ScalarFunctionExpr::try_new(
440 Arc::clone(func),
441 physical_args,
442 input_schema,
443 config_options,
444 )?))
445 }
446 Expr::Between(Between {
447 expr,
448 negated,
449 low,
450 high,
451 }) => {
452 let value_expr = create_physical_expr(
453 expr,
454 input_dfschema,
455 execution_props,
456 planning_ctx,
457 )?;
458 let low_expr =
459 create_physical_expr(low, input_dfschema, execution_props, planning_ctx)?;
460 let high_expr = create_physical_expr(
461 high,
462 input_dfschema,
463 execution_props,
464 planning_ctx,
465 )?;
466
467 let binary_expr = binary(
469 binary(
470 Arc::clone(&value_expr),
471 Operator::GtEq,
472 low_expr,
473 input_schema,
474 )?,
475 Operator::And,
476 binary(
477 Arc::clone(&value_expr),
478 Operator::LtEq,
479 high_expr,
480 input_schema,
481 )?,
482 input_schema,
483 );
484
485 if *negated {
486 expressions::not(binary_expr?)
487 } else {
488 binary_expr
489 }
490 }
491 Expr::InList(InList {
492 expr,
493 list,
494 negated,
495 }) => match expr.as_ref() {
496 Expr::Literal(ScalarValue::Utf8(None), _) => {
497 Ok(expressions::lit(ScalarValue::Boolean(None)))
498 }
499 _ => {
500 let value_expr = create_physical_expr(
501 expr,
502 input_dfschema,
503 execution_props,
504 planning_ctx,
505 )?;
506
507 let list_exprs = create_physical_exprs(
508 list,
509 input_dfschema,
510 execution_props,
511 planning_ctx,
512 )?;
513 expressions::in_list(value_expr, list_exprs, negated, input_schema)
514 }
515 },
516 Expr::ScalarSubquery(sq) => {
517 match planning_ctx.index_of(sq) {
518 Some(index) => {
519 let schema = sq.subquery.schema();
520 if schema.fields().len() != 1 {
521 return plan_err!(
522 "Scalar subquery must return exactly one column, got {}",
523 schema.fields().len()
524 );
525 }
526 let dt = schema.field(0).data_type().clone();
527 let nullable = schema.field(0).is_nullable();
528 Ok(Arc::new(ScalarSubqueryExpr::new(
529 dt,
530 nullable,
531 index,
532 planning_ctx.results().clone(),
533 )))
534 }
535 None => {
536 not_impl_err!(
540 "Physical plan does not support logical expression {e:?}"
541 )
542 }
543 }
544 }
545 Expr::Placeholder(Placeholder { id, .. }) => {
546 exec_err!("Placeholder '{id}' was not provided a value for execution.")
547 }
548 Expr::HigherOrderFunction(invocation @ HigherOrderFunction { func, args }) => {
549 let num_lambdas = args
550 .iter()
551 .filter(|arg| matches!(arg, Expr::Lambda(_)))
552 .count();
553
554 let mut lambda_parameters =
555 invocation.lambda_parameters(input_dfschema)?.into_iter();
556
557 if num_lambdas > lambda_parameters.len() {
558 return plan_err!(
559 "{} lambda_parameters returned only {} values for {num_lambdas} lambdas",
560 func.name(),
561 lambda_parameters.len()
562 );
563 }
564
565 let lambda_qualifier = 1 + input_dfschema
566 .iter()
567 .filter_map(|(qualifier, _field)| {
568 qualifier.and_then(|tbl| {
569 tbl.table().strip_prefix("lambda_")?.parse::<usize>().ok()
570 })
571 })
572 .max()
573 .unwrap_or_default();
574
575 let qualifier = TableReference::bare(format!("lambda_{lambda_qualifier}"));
576
577 let physical_args = args
578 .iter()
579 .map(|arg| match arg {
580 Expr::Lambda(lambda) => {
581 let lambda_parameters = lambda_parameters
582 .next()
583 .ok_or_else(|| {
584 internal_datafusion_err!(
585 "lambda_parameters len should have been checked above"
586 )
587 })?
588 .into_iter()
589 .zip(&lambda.params)
590 .map(|(field, name)| {
591 (Some(qualifier.clone()), field.renamed(name.as_str()))
592 });
593
594 let new_fields = input_dfschema
595 .iter()
596 .map(|(tbl, field)| (tbl.cloned(), Arc::clone(field)))
597 .chain(lambda_parameters)
598 .collect();
599
600 let lambda_schema = DFSchema::new_with_metadata(
601 new_fields,
602 input_dfschema.metadata().clone(),
603 )?;
604
605 let planning_ctx = planning_ctx
606 .clone()
607 .with_qualified_lambda_variables(&qualifier, &lambda.params);
608
609 create_physical_expr(
610 arg,
611 &lambda_schema,
612 execution_props,
613 &planning_ctx,
614 )
615 }
616 _ => create_physical_expr(
617 arg,
618 input_dfschema,
619 execution_props,
620 planning_ctx,
621 ),
622 })
623 .collect::<Result<_>>()?;
624
625 let config_options = match execution_props.config_options.as_ref() {
626 Some(config_options) => Arc::clone(config_options),
627 None => Arc::new(ConfigOptions::default()),
628 };
629
630 Ok(Arc::new(HigherOrderFunctionExpr::try_new_with_schema(
631 Arc::clone(func),
632 physical_args,
633 input_schema,
634 config_options,
635 )?))
636 }
637 Expr::Lambda(Lambda { params, body }) => expressions::lambda(
638 params,
639 create_physical_expr(body, input_dfschema, execution_props, planning_ctx)?,
640 ),
641 Expr::LambdaVariable(LambdaVariable {
642 name,
643 field,
644 spans: _,
645 }) => {
646 let field = field.as_ref().ok_or_else(|| {
647 plan_datafusion_err!("unresolved LambdaVariable {name}")
648 })?;
649
650 let qualifier =
651 planning_ctx
652 .lambda_variable_qualifier(name)
653 .ok_or_else(|| {
654 plan_datafusion_err!(
655 "qualifier for lambda variable {name} not found"
656 )
657 })?;
658
659 let index = input_dfschema
660 .index_of_column_by_name(Some(qualifier), name)
661 .ok_or_else(|| {
662 plan_datafusion_err!(
663 "lambda variable {qualifier}.{name} not found in planning schema"
664 )
665 })?;
666
667 let schema_field = input_dfschema.field(index);
668
669 let renamed_field = Arc::clone(field).renamed(name);
677
678 if &renamed_field != schema_field {
679 return plan_err!(
680 "LambdaVariable field and schema field mismatch {} != {}",
681 renamed_field,
682 schema_field
683 );
684 }
685
686 Ok(Arc::new(expressions::LambdaVariable::new(
687 index,
688 Arc::clone(schema_field),
689 )))
690 }
691 other => {
692 not_impl_err!("Physical plan does not support logical expression {other:?}")
693 }
694 }
695}
696
697pub fn create_physical_exprs<'a, I>(
701 exprs: I,
702 input_dfschema: &DFSchema,
703 execution_props: &ExecutionProps,
704 planning_ctx: &PhysicalPlanningContext,
705) -> Result<Vec<Arc<dyn PhysicalExpr>>>
706where
707 I: IntoIterator<Item = &'a Expr>,
708{
709 exprs
710 .into_iter()
711 .map(|expr| {
712 create_physical_expr(expr, input_dfschema, execution_props, planning_ctx)
713 })
714 .collect()
715}
716
717pub fn logical2physical(expr: &Expr, schema: &Schema) -> Arc<dyn PhysicalExpr> {
719 let df_schema = schema.clone().to_dfschema().unwrap();
721 let execution_props = ExecutionProps::new();
722 create_physical_expr(
723 expr,
724 &df_schema,
725 &execution_props,
726 &PhysicalPlanningContext::default(),
727 )
728 .unwrap()
729}
730
731#[cfg(test)]
732mod tests {
733 use arrow::array::{ArrayRef, BooleanArray, RecordBatch, StringArray};
734 use arrow::datatypes::{DataType, Field};
735 use arrow_schema::extension::{EXTENSION_TYPE_METADATA_KEY, EXTENSION_TYPE_NAME_KEY};
736 use datafusion_expr::col;
737
738 use super::*;
739
740 fn test_cast_schema() -> Schema {
741 Schema::new(vec![Field::new("a", DataType::Int32, false)])
742 }
743
744 fn lower_cast_expr(expr: &Expr, schema: &Schema) -> Result<Arc<dyn PhysicalExpr>> {
745 let df_schema = DFSchema::try_from(schema.clone())?;
746 create_physical_expr(
747 expr,
748 &df_schema,
749 &ExecutionProps::new(),
750 &PhysicalPlanningContext::default(),
751 )
752 }
753
754 fn as_planner_cast(physical: &Arc<dyn PhysicalExpr>) -> &expressions::CastExpr {
755 physical
756 .downcast_ref::<expressions::CastExpr>()
757 .expect("planner should lower logical CAST to CastExpr")
758 }
759
760 fn as_planner_try_cast(
761 physical: &Arc<dyn PhysicalExpr>,
762 ) -> &expressions::TryCastExpr {
763 physical
764 .downcast_ref::<expressions::TryCastExpr>()
765 .expect("planner should lower logical TRY_CAST to TryCastExpr")
766 }
767
768 #[test]
769 fn test_create_physical_expr_scalar_input_output() -> Result<()> {
770 let expr = col("letter").eq(lit("A"));
771
772 let schema = Schema::new(vec![Field::new("letter", DataType::Utf8, false)]);
773 let df_schema = DFSchema::try_from_qualified_schema("data", &schema)?;
774 let p = create_physical_expr(
775 &expr,
776 &df_schema,
777 &ExecutionProps::new(),
778 &PhysicalPlanningContext::default(),
779 )?;
780
781 let batch = RecordBatch::try_new(
782 Arc::new(schema),
783 vec![Arc::new(StringArray::from_iter_values(vec![
784 "A", "B", "C", "D",
785 ]))],
786 )?;
787 let result = p.evaluate(&batch)?;
788 let result = result.into_array(4).expect("Failed to convert to array");
789
790 assert_eq!(
791 &result,
792 &(Arc::new(BooleanArray::from(vec![true, false, false, false,])) as ArrayRef)
793 );
794
795 Ok(())
796 }
797
798 #[test]
799 fn test_cast_lowering_preserves_target_field_metadata() -> Result<()> {
800 let schema = test_cast_schema();
801
802 let target_field = Arc::new(
805 Field::new("cast_target", DataType::Int64, true).with_metadata(
806 [
807 (
808 EXTENSION_TYPE_NAME_KEY.to_string(),
809 "arrow.json".to_string(),
810 ),
811 (EXTENSION_TYPE_METADATA_KEY.to_string(), "{}".to_string()),
812 ("custom_target_meta".to_string(), "custom_value".to_string()),
813 ]
814 .into(),
815 ),
816 );
817 let cast_expr = Expr::Cast(Cast::new_from_field(
818 Box::new(col("a")),
819 Arc::clone(&target_field),
820 ));
821
822 let physical = lower_cast_expr(&cast_expr, &schema)?;
823 let cast = as_planner_cast(&physical);
824
825 assert_eq!(cast.cast_type(), &DataType::Int64);
827 let target_metadata = cast.target_metadata().expect("should have metadata");
828 assert_eq!(
829 target_metadata.get(EXTENSION_TYPE_NAME_KEY),
830 Some(&"arrow.json".to_string())
831 );
832 assert_eq!(
833 target_metadata.get(EXTENSION_TYPE_METADATA_KEY),
834 Some(&"{}".to_string())
835 );
836 assert_eq!(cast.target_nullable(), Some(true));
837
838 let returned = physical.return_field(&schema)?;
840 assert_eq!(
841 returned.metadata().get(EXTENSION_TYPE_NAME_KEY),
842 Some(&"arrow.json".to_string())
843 );
844 assert_eq!(
845 returned.metadata().get(EXTENSION_TYPE_METADATA_KEY),
846 Some(&"{}".to_string())
847 );
848 assert_eq!(
850 returned.metadata().get("custom_target_meta"),
851 Some(&"custom_value".to_string()),
852 "All target metadata should propagate with exact semantics"
853 );
854 assert!(physical.nullable(&schema)?);
855
856 Ok(())
857 }
858
859 #[test]
860 fn test_cast_lowering_preserves_standard_cast_semantics() -> Result<()> {
861 let schema = test_cast_schema();
862 let cast_expr = Expr::Cast(Cast::new(Box::new(col("a")), DataType::Int64));
863
864 let physical = lower_cast_expr(&cast_expr, &schema)?;
865 let cast = as_planner_cast(&physical);
866 let returned_field = physical.return_field(&schema)?;
867
868 assert_eq!(cast.cast_type(), &DataType::Int64);
869 assert_eq!(returned_field.name(), "a");
870 assert_eq!(returned_field.data_type(), &DataType::Int64);
871 assert!(!physical.nullable(&schema)?);
872
873 Ok(())
874 }
875
876 #[test]
877 fn test_cast_lowering_preserves_same_type_field_semantics() -> Result<()> {
878 let schema = test_cast_schema();
879
880 let target_field = Arc::new(
883 Field::new("same_type_cast", DataType::Int32, true).with_metadata(
884 [
885 (
886 EXTENSION_TYPE_NAME_KEY.to_string(),
887 "arrow.opaque".to_string(),
888 ),
889 ("custom_meta".to_string(), "custom_value".to_string()),
890 ]
891 .into(),
892 ),
893 );
894
895 for use_try_cast in [false, true] {
896 let cast_name = if use_try_cast { "TRY_CAST" } else { "CAST" };
898
899 let cast_expr = if use_try_cast {
900 Expr::TryCast(TryCast::new_from_field(
901 Box::new(col("a")),
902 Arc::clone(&target_field),
903 ))
904 } else {
905 Expr::Cast(Cast::new_from_field(
906 Box::new(col("a")),
907 Arc::clone(&target_field),
908 ))
909 };
910
911 let physical = lower_cast_expr(&cast_expr, &schema)?;
912
913 let (cast_type, target_metadata, target_nullable) = if use_try_cast {
915 let cast = as_planner_try_cast(&physical);
916 (cast.cast_type(), cast.target_metadata(), None)
917 } else {
918 let cast = as_planner_cast(&physical);
919 (
920 cast.cast_type(),
921 cast.target_metadata(),
922 cast.target_nullable(),
923 )
924 };
925
926 assert_eq!(cast_type, &DataType::Int32, "{cast_name}: cast_type");
928 let target_metadata = target_metadata.expect("should have metadata");
929 assert_eq!(
930 target_metadata.get(EXTENSION_TYPE_NAME_KEY),
931 Some(&"arrow.opaque".to_string()),
932 "{cast_name}: extension type name"
933 );
934
935 if !use_try_cast {
937 assert_eq!(target_nullable, Some(true), "{cast_name}: target_nullable");
938 }
939
940 let returned = physical.return_field(&schema)?;
942 assert_eq!(
943 returned.metadata().get(EXTENSION_TYPE_NAME_KEY),
944 Some(&"arrow.opaque".to_string()),
945 "{cast_name}: return_field extension type name"
946 );
947 assert_eq!(
949 returned.metadata().get("custom_meta"),
950 Some(&"custom_value".to_string()),
951 "{cast_name}: All target metadata should propagate with exact semantics"
952 );
953 assert!(
954 physical.nullable(&schema)?,
955 "{cast_name}: should be nullable"
956 );
957 }
958
959 Ok(())
960 }
961
962 #[test]
967 #[cfg_attr(not(feature = "recursive_protection"), ignore)]
968 fn test_deeply_nested_binary_expr() -> Result<()> {
969 let depth = 1000;
972
973 let mut expr = col("a");
974 for _ in 0..depth {
975 expr = Expr::BinaryExpr(BinaryExpr {
976 left: Box::new(expr),
977 op: Operator::Plus,
978 right: Box::new(col("a")),
979 });
980 }
981
982 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
983 let df_schema = DFSchema::try_from(schema)?;
984
985 let _physical_expr = create_physical_expr(
987 &expr,
988 &df_schema,
989 &ExecutionProps::new(),
990 &PhysicalPlanningContext::default(),
991 )?;
992
993 Ok(())
994 }
995}