1use std::borrow::Borrow;
25use std::pin::Pin;
26use std::sync::Arc;
27use std::task::{Context, Poll};
28
29use super::{
30 DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties, Partitioning,
31 PlanProperties, RecordBatchStream, SendableRecordBatchStream, Statistics,
32 metrics::{ExecutionPlanMetricsSet, MetricsSet},
33};
34use crate::execution_plan::{
35 CardinalityEffect, InvariantLevel, boundedness_from_children,
36 check_default_invariants, emission_type_from_children,
37};
38use crate::filter::FilterExec;
39use crate::filter_pushdown::{
40 ChildPushdownResult, FilterDescription, FilterPushdownPhase,
41 FilterPushdownPropagation, PushedDown,
42};
43use crate::metrics::BaselineMetrics;
44use crate::projection::{ProjectionExec, ProjectionExpr, make_with_child};
45use crate::statistics::{ChildStats, StatisticsArgs};
46use crate::stream::ObservedStream;
47use crate::{ChildrenPropertiesMode, ReplaceChildrenOptions, validate_child_count};
48
49use arrow::datatypes::{Field, Schema, SchemaRef};
50use arrow::record_batch::RecordBatch;
51use datafusion_common::config::ConfigOptions;
52use datafusion_common::stats::NdvFallback;
53use datafusion_common::tree_node::TreeNodeRecursion;
54use datafusion_common::{
55 Result, assert_or_internal_err, exec_err, internal_datafusion_err, plan_err,
56};
57use datafusion_execution::TaskContext;
58use datafusion_physical_expr::expressions::{CastExpr, Column};
59use datafusion_physical_expr::{
60 EquivalenceProperties, PhysicalExpr, calculate_union, conjunction,
61};
62
63use futures::Stream;
64use itertools::Itertools;
65use log::{debug, trace, warn};
66use tokio::macros::support::thread_rng_n;
67
68fn coerce_schema(
89 input: Arc<dyn ExecutionPlan>,
90 schema: &SchemaRef,
91) -> Result<Arc<dyn ExecutionPlan>> {
92 let input_schema = input.schema();
93 if &input_schema == schema {
94 return Ok(input);
95 }
96
97 let exprs = input_schema
98 .fields()
99 .iter()
100 .zip(schema.fields())
101 .enumerate()
102 .map(|(i, (input_field, target_field))| {
103 if input_field.data_type() != target_field.data_type() {
104 return plan_err!(
105 "UnionExec/InterleaveExec requires all inputs to have the same \
106 data type per column; column {i} has type {} in one input, but \
107 the union schema expects {}",
108 input_field.data_type(),
109 target_field.data_type()
110 );
111 }
112 let column: Arc<dyn PhysicalExpr> =
113 Arc::new(Column::new(input_field.name(), i));
114 let expr = if input_field == target_field {
115 column
116 } else {
117 Arc::new(CastExpr::new_with_target_field(
118 column,
119 Arc::clone(target_field),
120 None,
121 )) as Arc<dyn PhysicalExpr>
122 };
123 Ok(ProjectionExpr {
124 expr,
125 alias: target_field.name().clone(),
126 })
127 })
128 .collect::<Result<Vec<_>>>()?;
129
130 Ok(Arc::new(ProjectionExec::try_new(exprs, input)?))
131}
132
133#[derive(Debug, Clone)]
171pub struct UnionExec {
172 inputs: Vec<Arc<dyn ExecutionPlan>>,
174 metrics: ExecutionPlanMetricsSet,
176 cache: Arc<PlanProperties>,
178}
179
180impl UnionExec {
181 pub fn try_new(
190 inputs: Vec<Arc<dyn ExecutionPlan>>,
191 ) -> Result<Arc<dyn ExecutionPlan>> {
192 match inputs.len() {
193 0 => exec_err!("UnionExec requires at least one input"),
194 1 => Ok(inputs.into_iter().next().unwrap()),
195 _ => {
196 let schema = union_schema(&inputs)?;
197 let inputs = inputs
201 .into_iter()
202 .map(|input| coerce_schema(input, &schema))
203 .collect::<Result<Vec<_>>>()?;
204 let cache = Self::compute_properties(&inputs, schema)?;
205 Ok(Arc::new(UnionExec {
206 inputs,
207 metrics: ExecutionPlanMetricsSet::new(),
208 cache: Arc::new(cache),
209 }))
210 }
211 }
212 }
213
214 pub fn inputs(&self) -> &Vec<Arc<dyn ExecutionPlan>> {
216 &self.inputs
217 }
218
219 fn owning_input(&self, partition: usize) -> Option<(usize, usize)> {
222 let mut remaining = partition;
223 for (i, input) in self.inputs.iter().enumerate() {
224 let count = input.output_partitioning().partition_count();
225 if remaining < count {
226 return Some((i, remaining));
227 }
228 remaining -= count;
229 }
230 None
231 }
232
233 fn compute_properties(
235 inputs: &[Arc<dyn ExecutionPlan>],
236 schema: SchemaRef,
237 ) -> Result<PlanProperties> {
238 let children_eqps = inputs
240 .iter()
241 .map(|child| child.equivalence_properties().clone())
242 .collect::<Vec<_>>();
243 let eq_properties = calculate_union(children_eqps, schema)?;
244
245 let num_partitions = inputs
247 .iter()
248 .map(|plan| plan.output_partitioning().partition_count())
249 .sum();
250 let output_partitioning = Partitioning::UnknownPartitioning(num_partitions);
251 Ok(PlanProperties::new(
252 eq_properties,
253 output_partitioning,
254 emission_type_from_children(inputs),
255 boundedness_from_children(inputs),
256 ))
257 }
258}
259
260impl DisplayAs for UnionExec {
261 fn fmt_as(
262 &self,
263 t: DisplayFormatType,
264 f: &mut std::fmt::Formatter,
265 ) -> std::fmt::Result {
266 match t {
267 DisplayFormatType::Default | DisplayFormatType::Verbose => {
268 write!(f, "UnionExec")
269 }
270 DisplayFormatType::TreeRender => Ok(()),
271 }
272 }
273}
274
275impl ExecutionPlan for UnionExec {
276 fn name(&self) -> &'static str {
277 "UnionExec"
278 }
279
280 fn properties(&self) -> &Arc<PlanProperties> {
282 &self.cache
283 }
284
285 fn check_invariants(&self, check: InvariantLevel) -> Result<()> {
286 check_default_invariants(self, check)?;
287
288 (self.inputs().len() >= 2).then_some(()).ok_or_else(|| {
289 internal_datafusion_err!("UnionExec should have at least 2 children")
290 })
291 }
292
293 fn maintains_input_order(&self) -> Vec<bool> {
294 if let Some(output_ordering) = self.properties().output_ordering() {
303 self.inputs()
304 .iter()
305 .map(|child| {
306 if let Some(child_ordering) = child.output_ordering() {
307 output_ordering.len() == child_ordering.len()
308 } else {
309 false
310 }
311 })
312 .collect()
313 } else {
314 vec![false; self.inputs().len()]
315 }
316 }
317
318 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
319 vec![false; self.children().len()]
320 }
321
322 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
323 self.inputs.iter().collect()
324 }
325
326 fn apply_expressions(
327 &self,
328 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
329 ) -> Result<TreeNodeRecursion> {
330 Ok(TreeNodeRecursion::Continue)
331 }
332
333 fn replace_children(
334 self: Arc<Self>,
335 children: Vec<Arc<dyn ExecutionPlan>>,
336 options: ReplaceChildrenOptions,
337 ) -> Result<Arc<dyn ExecutionPlan>> {
338 validate_child_count!(self, children);
339 match options.children_properties {
340 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
341 inputs: children,
342 metrics: ExecutionPlanMetricsSet::new(),
343 ..Self::clone(&*self)
344 })),
345 ChildrenPropertiesMode::Recompute => UnionExec::try_new(children),
346 }
347 }
348
349 fn with_new_children(
350 self: Arc<Self>,
351 children: Vec<Arc<dyn ExecutionPlan>>,
352 ) -> Result<Arc<dyn ExecutionPlan>> {
353 self.replace_children(
354 children,
355 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
356 )
357 }
358
359 fn with_new_children_and_same_properties(
360 self: Arc<Self>,
361 children: Vec<Arc<dyn ExecutionPlan>>,
362 ) -> Result<Arc<dyn ExecutionPlan>> {
363 self.replace_children(
364 children,
365 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
366 )
367 }
368
369 fn execute(
370 &self,
371 mut partition: usize,
372 context: Arc<TaskContext>,
373 ) -> Result<SendableRecordBatchStream> {
374 trace!(
375 "Start UnionExec::execute for partition {} of context session_id {} and task_id {:?}",
376 partition,
377 context.session_id(),
378 context.task_id()
379 );
380 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
381 let elapsed_compute = baseline_metrics.elapsed_compute().clone();
384 let _timer = elapsed_compute.timer(); for input in self.inputs.iter() {
388 if partition < input.output_partitioning().partition_count() {
390 let stream = input.execute(partition, context)?;
391 debug!("Found a Union partition to execute");
392 return Ok(Box::pin(ObservedStream::new(
393 stream,
394 baseline_metrics,
395 None,
396 )));
397 } else {
398 partition -= input.output_partitioning().partition_count();
399 }
400 }
401
402 warn!("Error in Union: Partition {partition} not found");
403
404 exec_err!("Partition {partition} not found in Union")
405 }
406
407 fn metrics(&self) -> Option<MetricsSet> {
408 Some(self.metrics.clone_inner())
409 }
410
411 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
412 if let Some(partition_idx) = partition {
413 let targeted = self.owning_input(partition_idx);
416 self.inputs
417 .iter()
418 .enumerate()
419 .map(|(i, _)| match targeted {
420 Some((target_i, target_partition)) if i == target_i => {
421 ChildStats::At(Some(target_partition))
422 }
423 _ => ChildStats::Skip,
424 })
425 .collect()
426 } else {
427 vec![ChildStats::At(None); self.inputs.len()]
428 }
429 }
430
431 fn statistics_from_inputs(
432 &self,
433 input_stats: &[Arc<Statistics>],
434 args: &StatisticsArgs,
435 ) -> Result<Arc<Statistics>> {
436 if let Some(partition_idx) = args.partition() {
437 if let Some((target_i, _)) = self.owning_input(partition_idx) {
439 return Ok(Arc::clone(&input_stats[target_i]));
441 }
442 Ok(Arc::new(Statistics::new_unknown(&self.schema())))
444 } else {
445 let stats_refs = input_stats.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
446
447 Ok(Arc::new(Statistics::try_merge_iter_with_ndv_fallback(
448 stats_refs,
449 self.schema().as_ref(),
450 NdvFallback::Sum,
451 )?))
452 }
453 }
454
455 fn cardinality_effect(&self) -> CardinalityEffect {
456 CardinalityEffect::GreaterEqual
459 }
460
461 fn supports_limit_pushdown(&self) -> bool {
462 true
463 }
464
465 fn try_swapping_with_projection(
469 &self,
470 projection: &ProjectionExec,
471 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
472 if projection.expr().len() >= projection.input().schema().fields().len() {
474 return Ok(None);
475 }
476
477 let new_children = self
478 .children()
479 .into_iter()
480 .map(|child| make_with_child(projection, child))
481 .collect::<Result<Vec<_>>>()?;
482
483 Ok(Some(UnionExec::try_new(new_children.clone())?))
484 }
485
486 fn gather_filters_for_pushdown(
487 &self,
488 _phase: FilterPushdownPhase,
489 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
490 _config: &ConfigOptions,
491 ) -> Result<FilterDescription> {
492 FilterDescription::from_children(parent_filters, &self.children())
493 }
494
495 fn handle_child_pushdown_result(
496 &self,
497 phase: FilterPushdownPhase,
498 child_pushdown_result: ChildPushdownResult,
499 _config: &ConfigOptions,
500 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
501 if phase != FilterPushdownPhase::Pre {
506 return Ok(FilterPushdownPropagation::if_all(child_pushdown_result));
507 }
508
509 let mut unsupported_filters_per_child = vec![Vec::new(); self.inputs.len()];
526 for parent_filter_result in child_pushdown_result.parent_filters.iter() {
527 for (child_idx, &child_result) in
528 parent_filter_result.child_results.iter().enumerate()
529 {
530 if matches!(child_result, PushedDown::No) {
531 unsupported_filters_per_child[child_idx]
532 .push(Arc::clone(&parent_filter_result.filter));
533 }
534 }
535 }
536
537 let mut new_children = self.inputs.clone();
539 for (child_idx, unsupported_filters) in
540 unsupported_filters_per_child.iter().enumerate()
541 {
542 if !unsupported_filters.is_empty() {
543 let combined_filter = conjunction(unsupported_filters.clone());
544 new_children[child_idx] = Arc::new(FilterExec::try_new(
545 combined_filter,
546 Arc::clone(&self.inputs[child_idx]),
547 )?);
548 }
549 }
550
551 let children_modified = new_children
553 .iter()
554 .zip(self.inputs.iter())
555 .any(|(new, old)| !Arc::ptr_eq(new, old));
556
557 let all_filters_pushed =
558 vec![PushedDown::Yes; child_pushdown_result.parent_filters.len()];
559 let propagation = if children_modified {
560 let updated_node = UnionExec::try_new(new_children)?;
561 FilterPushdownPropagation::with_parent_pushdown_result(all_filters_pushed)
562 .with_updated_node(updated_node)
563 } else {
564 FilterPushdownPropagation::with_parent_pushdown_result(all_filters_pushed)
565 };
566
567 Ok(propagation)
570 }
571 #[cfg(feature = "proto")]
572 fn try_to_proto(
573 &self,
574 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
575 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
576 use datafusion_proto_models::protobuf;
577 let inputs = ctx.encode_children(self.inputs())?;
578 Ok(Some(protobuf::PhysicalPlanNode {
579 physical_plan_type: Some(
580 protobuf::physical_plan_node::PhysicalPlanType::Union(
581 protobuf::UnionExecNode { inputs },
582 ),
583 ),
584 }))
585 }
586}
587
588#[cfg(feature = "proto")]
589impl UnionExec {
590 pub fn try_from_proto(
591 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
592 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
593 ) -> Result<Arc<dyn ExecutionPlan>> {
594 use datafusion_proto_models::protobuf;
595 let union = crate::expect_plan_variant!(
596 node,
597 protobuf::physical_plan_node::PhysicalPlanType::Union,
598 "UnionExec",
599 );
600 let inputs = union
601 .inputs
602 .iter()
603 .map(|input| ctx.decode_child(input))
604 .collect::<Result<Vec<_>>>()?;
605 UnionExec::try_new(inputs)
606 }
607}
608
609#[derive(Debug, Clone)]
645pub struct InterleaveExec {
646 inputs: Vec<Arc<dyn ExecutionPlan>>,
648 metrics: ExecutionPlanMetricsSet,
650 cache: Arc<PlanProperties>,
652}
653
654impl InterleaveExec {
655 pub fn try_new(inputs: Vec<Arc<dyn ExecutionPlan>>) -> Result<Self> {
657 assert_or_internal_err!(
658 can_interleave(inputs.iter()),
659 "Not all InterleaveExec children have a consistent hash or range partitioning"
660 );
661 let schema = union_schema(&inputs)?;
662 let inputs = inputs
663 .into_iter()
664 .map(|input| coerce_schema(input, &schema))
665 .collect::<Result<Vec<_>>>()?;
666 let cache = Self::compute_properties(&inputs, schema)?;
667 Ok(InterleaveExec {
668 inputs,
669 metrics: ExecutionPlanMetricsSet::new(),
670 cache: Arc::new(cache),
671 })
672 }
673
674 pub fn inputs(&self) -> &Vec<Arc<dyn ExecutionPlan>> {
676 &self.inputs
677 }
678
679 fn compute_properties(
681 inputs: &[Arc<dyn ExecutionPlan>],
682 schema: SchemaRef,
683 ) -> Result<PlanProperties> {
684 let eq_properties = EquivalenceProperties::new(schema);
685 let output_partitioning = inputs[0].output_partitioning().clone();
687 Ok(PlanProperties::new(
688 eq_properties,
689 output_partitioning,
690 emission_type_from_children(inputs),
691 boundedness_from_children(inputs),
692 ))
693 }
694}
695
696impl DisplayAs for InterleaveExec {
697 fn fmt_as(
698 &self,
699 t: DisplayFormatType,
700 f: &mut std::fmt::Formatter,
701 ) -> std::fmt::Result {
702 match t {
703 DisplayFormatType::Default | DisplayFormatType::Verbose => {
704 write!(f, "InterleaveExec")
705 }
706 DisplayFormatType::TreeRender => Ok(()),
707 }
708 }
709}
710
711impl ExecutionPlan for InterleaveExec {
712 fn name(&self) -> &'static str {
713 "InterleaveExec"
714 }
715
716 fn properties(&self) -> &Arc<PlanProperties> {
718 &self.cache
719 }
720
721 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
722 self.inputs.iter().collect()
723 }
724
725 fn maintains_input_order(&self) -> Vec<bool> {
726 vec![false; self.inputs().len()]
727 }
728
729 fn apply_expressions(
730 &self,
731 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
732 ) -> Result<TreeNodeRecursion> {
733 Ok(TreeNodeRecursion::Continue)
734 }
735
736 fn replace_children(
737 self: Arc<Self>,
738 children: Vec<Arc<dyn ExecutionPlan>>,
739 options: ReplaceChildrenOptions,
740 ) -> Result<Arc<dyn ExecutionPlan>> {
741 validate_child_count!(self, children);
742 match options.children_properties {
743 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
744 inputs: children,
745 metrics: ExecutionPlanMetricsSet::new(),
746 ..Self::clone(&*self)
747 })),
748 ChildrenPropertiesMode::Recompute => {
749 assert_or_internal_err!(
751 can_interleave(children.iter()),
752 "Can not create InterleaveExec: new children can not be interleaved"
753 );
754 Ok(Arc::new(InterleaveExec::try_new(children)?))
755 }
756 }
757 }
758
759 fn with_new_children(
760 self: Arc<Self>,
761 children: Vec<Arc<dyn ExecutionPlan>>,
762 ) -> Result<Arc<dyn ExecutionPlan>> {
763 self.replace_children(
764 children,
765 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
766 )
767 }
768
769 fn with_new_children_and_same_properties(
770 self: Arc<Self>,
771 children: Vec<Arc<dyn ExecutionPlan>>,
772 ) -> Result<Arc<dyn ExecutionPlan>> {
773 self.replace_children(
774 children,
775 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
776 )
777 }
778
779 fn execute(
780 &self,
781 partition: usize,
782 context: Arc<TaskContext>,
783 ) -> Result<SendableRecordBatchStream> {
784 trace!(
785 "Start InterleaveExec::execute for partition {} of context session_id {} and task_id {:?}",
786 partition,
787 context.session_id(),
788 context.task_id()
789 );
790 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
791 let elapsed_compute = baseline_metrics.elapsed_compute().clone();
794 let _timer = elapsed_compute.timer(); let mut input_stream_vec = vec![];
797 for input in self.inputs.iter() {
798 if partition < input.output_partitioning().partition_count() {
799 let stream = input.execute(partition, Arc::clone(&context))?;
800 input_stream_vec.push(stream);
801 } else {
802 break;
804 }
805 }
806 if input_stream_vec.len() == self.inputs.len() {
807 let stream = Box::pin(CombinedRecordBatchStream::new(
808 self.schema(),
809 input_stream_vec,
810 ));
811 return Ok(Box::pin(ObservedStream::new(
812 stream,
813 baseline_metrics,
814 None,
815 )));
816 }
817
818 warn!("Error in InterleaveExec: Partition {partition} not found");
819
820 exec_err!("Partition {partition} not found in InterleaveExec")
821 }
822
823 fn metrics(&self) -> Option<MetricsSet> {
824 Some(self.metrics.clone_inner())
825 }
826
827 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
828 vec![ChildStats::At(partition); self.inputs.len()]
829 }
830
831 fn statistics_from_inputs(
832 &self,
833 input_stats: &[Arc<Statistics>],
834 _args: &StatisticsArgs,
835 ) -> Result<Arc<Statistics>> {
836 let stats = input_stats
837 .iter()
838 .map(|s| s.as_ref().clone())
839 .collect::<Vec<_>>();
840
841 Ok(Arc::new(Statistics::try_merge_iter_with_ndv_fallback(
842 stats.iter(),
843 self.schema().as_ref(),
844 NdvFallback::Sum,
845 )?))
846 }
847
848 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
849 vec![false; self.children().len()]
850 }
851 #[cfg(feature = "proto")]
852 fn try_to_proto(
853 &self,
854 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
855 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
856 use datafusion_proto_models::protobuf;
857 let inputs = ctx.encode_children(self.inputs())?;
858 Ok(Some(protobuf::PhysicalPlanNode {
859 physical_plan_type: Some(
860 protobuf::physical_plan_node::PhysicalPlanType::Interleave(
861 protobuf::InterleaveExecNode { inputs },
862 ),
863 ),
864 }))
865 }
866}
867
868#[cfg(feature = "proto")]
869impl InterleaveExec {
870 pub fn try_from_proto(
871 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
872 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
873 ) -> Result<Arc<dyn ExecutionPlan>> {
874 use datafusion_proto_models::protobuf;
875 let interleave = crate::expect_plan_variant!(
876 node,
877 protobuf::physical_plan_node::PhysicalPlanType::Interleave,
878 "InterleaveExec",
879 );
880 let inputs = interleave
881 .inputs
882 .iter()
883 .map(|input| ctx.decode_child(input))
884 .collect::<Result<Vec<_>>>()?;
885 Ok(Arc::new(InterleaveExec::try_new(inputs)?))
886 }
887}
888
889pub fn can_interleave<T: Borrow<Arc<dyn ExecutionPlan>>>(
900 mut inputs: impl Iterator<Item = T>,
901) -> bool {
902 let Some(first) = inputs.next() else {
903 return false;
904 };
905
906 let reference = first.borrow().output_partitioning();
907 matches!(reference, Partitioning::Hash(_, _) | Partitioning::Range(_))
908 && inputs
909 .map(|plan| plan.borrow().output_partitioning().clone())
910 .all(|partition| partition == *reference)
911}
912
913fn union_schema(inputs: &[Arc<dyn ExecutionPlan>]) -> Result<SchemaRef> {
914 if inputs.is_empty() {
915 return exec_err!("Cannot create union schema from empty inputs");
916 }
917
918 let first_schema = inputs[0].schema();
919 let first_field_count = first_schema.fields().len();
920
921 for (idx, input) in inputs.iter().enumerate().skip(1) {
923 let field_count = input.schema().fields().len();
924 if field_count != first_field_count {
925 return exec_err!(
926 "UnionExec/InterleaveExec requires all inputs to have the same number of fields. \
927 Input 0 has {first_field_count} fields, but input {idx} has {field_count} fields"
928 );
929 }
930 }
931
932 let fields = (0..first_field_count)
933 .map(|i| {
934 let base_field = first_schema.field(i).clone();
937
938 inputs
941 .iter()
942 .enumerate()
943 .map(|(input_idx, input)| {
944 let field = input.schema().field(i).clone();
945 let mut metadata = field.metadata().clone();
946
947 let other_metadatas = inputs
948 .iter()
949 .enumerate()
950 .filter(|(other_idx, _)| *other_idx != input_idx)
951 .flat_map(|(_, other_input)| {
952 other_input.schema().field(i).metadata().clone().into_iter()
953 });
954
955 metadata.extend(other_metadatas);
956 field.with_metadata(metadata)
957 })
958 .find_or_first(Field::is_nullable)
959 .unwrap()
962 .with_name(base_field.name())
963 })
964 .collect::<Vec<_>>();
965
966 let all_metadata_merged = inputs
967 .iter()
968 .flat_map(|i| i.schema().metadata().clone().into_iter())
969 .collect();
970
971 Ok(Arc::new(Schema::new_with_metadata(
972 fields,
973 all_metadata_merged,
974 )))
975}
976
977struct CombinedRecordBatchStream {
979 schema: SchemaRef,
981 entries: Vec<SendableRecordBatchStream>,
983}
984
985impl CombinedRecordBatchStream {
986 pub fn new(schema: SchemaRef, entries: Vec<SendableRecordBatchStream>) -> Self {
988 Self { schema, entries }
989 }
990}
991
992impl RecordBatchStream for CombinedRecordBatchStream {
993 fn schema(&self) -> SchemaRef {
994 Arc::clone(&self.schema)
995 }
996}
997
998impl Stream for CombinedRecordBatchStream {
999 type Item = Result<RecordBatch>;
1000
1001 fn poll_next(
1002 mut self: Pin<&mut Self>,
1003 cx: &mut Context<'_>,
1004 ) -> Poll<Option<Self::Item>> {
1005 use Poll::*;
1006
1007 let start = thread_rng_n(self.entries.len() as u32) as usize;
1008 let mut idx = start;
1009
1010 for _ in 0..self.entries.len() {
1011 let stream = self.entries.get_mut(idx).unwrap();
1012
1013 match Pin::new(stream).poll_next(cx) {
1014 Ready(Some(val)) => return Ready(Some(val)),
1015 Ready(None) => {
1016 self.entries.swap_remove(idx);
1018
1019 if idx == self.entries.len() {
1022 idx = 0;
1023 } else if idx < start && start <= self.entries.len() {
1024 idx = idx.wrapping_add(1) % self.entries.len();
1027 }
1028 }
1029 Pending => {
1030 idx = idx.wrapping_add(1) % self.entries.len();
1031 }
1032 }
1033 }
1034
1035 if self.entries.is_empty() {
1037 Ready(None)
1038 } else {
1039 Pending
1040 }
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use super::*;
1047 use crate::collect;
1048 use crate::repartition::RepartitionExec;
1049 use crate::statistics::{StatisticsArgs, StatisticsContext};
1050 use crate::test::exec::StatisticsExec;
1051 use crate::test::{self, TestMemoryExec};
1052
1053 use arrow::compute::SortOptions;
1054 use arrow::datatypes::DataType;
1055 use datafusion_common::SplitPoint;
1056 use datafusion_common::stats::Precision;
1057 use datafusion_common::{ColumnStatistics, ScalarValue};
1058 use datafusion_physical_expr::RangePartitioning;
1059 use datafusion_physical_expr::equivalence::convert_to_orderings;
1060 use datafusion_physical_expr::expressions::col;
1061 use datafusion_physical_expr_common::sort_expr::{LexOrdering, PhysicalSortExpr};
1062
1063 fn create_test_schema() -> Result<SchemaRef> {
1065 let a = Field::new("a", DataType::Int32, true);
1066 let b = Field::new("b", DataType::Int32, true);
1067 let c = Field::new("c", DataType::Int32, true);
1068 let d = Field::new("d", DataType::Int32, true);
1069 let e = Field::new("e", DataType::Int32, true);
1070 let f = Field::new("f", DataType::Int32, true);
1071 let g = Field::new("g", DataType::Int32, true);
1072 let schema = Arc::new(Schema::new(vec![a, b, c, d, e, f, g]));
1073
1074 Ok(schema)
1075 }
1076
1077 fn create_test_schema2() -> Result<SchemaRef> {
1078 let a = Field::new("a", DataType::Int32, true);
1079 let b = Field::new("b", DataType::Int32, true);
1080 let c = Field::new("c", DataType::Int32, true);
1081 let d = Field::new("d", DataType::Int32, true);
1082 let e = Field::new("e", DataType::Int32, true);
1083 let f = Field::new("f", DataType::Int32, true);
1084 let schema = Arc::new(Schema::new(vec![a, b, c, d, e, f]));
1085
1086 Ok(schema)
1087 }
1088
1089 #[tokio::test]
1090 async fn test_union_partitions() -> Result<()> {
1091 let task_ctx = Arc::new(TaskContext::default());
1092
1093 let csv = test::scan_partitioned(4);
1095 let csv2 = test::scan_partitioned(5);
1096
1097 let union_exec: Arc<dyn ExecutionPlan> = UnionExec::try_new(vec![csv, csv2])?;
1098
1099 assert_eq!(
1101 union_exec
1102 .properties()
1103 .output_partitioning()
1104 .partition_count(),
1105 9
1106 );
1107
1108 let result: Vec<RecordBatch> = collect(union_exec, task_ctx).await?;
1109 assert_eq!(result.len(), 9);
1110
1111 Ok(())
1112 }
1113
1114 #[tokio::test]
1115 async fn test_interleave_conforms_batch_schema() -> Result<()> {
1116 let task_ctx = Arc::new(TaskContext::default());
1121
1122 let schema_not_null =
1123 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1124 let batch_not_null = RecordBatch::try_new(
1125 Arc::clone(&schema_not_null),
1126 vec![Arc::new(arrow::array::Int32Array::from(vec![1, 2]))],
1127 )?;
1128
1129 let schema_nullable =
1130 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
1131 let batch_nullable = RecordBatch::try_new(
1132 Arc::clone(&schema_nullable),
1133 vec![Arc::new(arrow::array::Int32Array::from(vec![3, 4]))],
1134 )?;
1135
1136 let hash_expr = vec![col("a", schema_not_null.as_ref())?];
1137 let left: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1138 TestMemoryExec::try_new_exec(&[vec![batch_not_null]], schema_not_null, None)?,
1139 Partitioning::Hash(hash_expr.clone(), 1),
1140 )?);
1141 let right: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1142 TestMemoryExec::try_new_exec(&[vec![batch_nullable]], schema_nullable, None)?,
1143 Partitioning::Hash(hash_expr, 1),
1144 )?);
1145
1146 let interleave: Arc<dyn ExecutionPlan> =
1147 Arc::new(InterleaveExec::try_new(vec![left, right])?);
1148 let interleave_schema = interleave.schema();
1149 assert!(interleave_schema.field(0).is_nullable());
1150
1151 let batches = collect(interleave, task_ctx).await?;
1152 assert!(!batches.is_empty());
1153 for batch in &batches {
1154 assert_eq!(batch.schema(), interleave_schema);
1155 }
1156
1157 Ok(())
1158 }
1159
1160 fn stats_merge_inputs() -> (SchemaRef, Statistics, Statistics, Statistics) {
1161 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::UInt32, true)]));
1162
1163 let left = Statistics::default()
1164 .with_num_rows(Precision::Exact(5))
1165 .with_total_byte_size(Precision::Exact(23))
1166 .add_column_statistics(
1167 ColumnStatistics::new_unknown()
1168 .with_distinct_count(Precision::Exact(5))
1169 .with_min_value(Precision::Exact(ScalarValue::UInt32(Some(1))))
1170 .with_max_value(Precision::Exact(ScalarValue::UInt32(Some(21))))
1171 .with_sum_value(Precision::Exact(ScalarValue::UInt32(Some(42))))
1172 .with_null_count(Precision::Exact(0))
1173 .with_byte_size(Precision::Exact(40)),
1174 );
1175
1176 let right = Statistics::default()
1177 .with_num_rows(Precision::Exact(7))
1178 .with_total_byte_size(Precision::Exact(29))
1179 .add_column_statistics(
1180 ColumnStatistics::new_unknown()
1181 .with_distinct_count(Precision::Exact(3))
1182 .with_min_value(Precision::Exact(ScalarValue::UInt32(Some(22))))
1183 .with_max_value(Precision::Exact(ScalarValue::UInt32(Some(34))))
1184 .with_sum_value(Precision::Exact(ScalarValue::UInt32(Some(8))))
1185 .with_null_count(Precision::Exact(1))
1186 .with_byte_size(Precision::Exact(60)),
1187 );
1188
1189 let expected = Statistics::default()
1190 .with_num_rows(Precision::Exact(12))
1191 .with_total_byte_size(Precision::Exact(52))
1192 .add_column_statistics(
1193 ColumnStatistics::new_unknown()
1194 .with_distinct_count(Precision::Inexact(8))
1195 .with_min_value(Precision::Exact(ScalarValue::UInt32(Some(1))))
1196 .with_max_value(Precision::Exact(ScalarValue::UInt32(Some(34))))
1197 .with_sum_value(Precision::Exact(ScalarValue::UInt64(Some(50))))
1198 .with_null_count(Precision::Exact(1))
1199 .with_byte_size(Precision::Exact(100)),
1200 );
1201
1202 (schema, left, right, expected)
1203 }
1204
1205 fn stats_merge_multicolumn_inputs() -> (SchemaRef, Statistics, Statistics, Statistics)
1206 {
1207 let schema = Arc::new(Schema::new(vec![
1208 Field::new("a", DataType::Int64, true),
1209 Field::new("b", DataType::Utf8, true),
1210 Field::new("c", DataType::Float32, true),
1211 ]));
1212
1213 let left = Statistics::default()
1214 .with_num_rows(Precision::Exact(5))
1215 .with_total_byte_size(Precision::Exact(23))
1216 .add_column_statistics(
1217 ColumnStatistics::new_unknown()
1218 .with_distinct_count(Precision::Exact(5))
1219 .with_min_value(Precision::Exact(ScalarValue::Int64(Some(-4))))
1220 .with_max_value(Precision::Exact(ScalarValue::Int64(Some(21))))
1221 .with_sum_value(Precision::Exact(ScalarValue::Int64(Some(42))))
1222 .with_null_count(Precision::Exact(0)),
1223 )
1224 .add_column_statistics(
1225 ColumnStatistics::new_unknown()
1226 .with_distinct_count(Precision::Exact(2))
1227 .with_min_value(Precision::Exact(ScalarValue::from("a")))
1228 .with_max_value(Precision::Exact(ScalarValue::from("x")))
1229 .with_null_count(Precision::Exact(3)),
1230 )
1231 .add_column_statistics(
1232 ColumnStatistics::new_unknown()
1233 .with_max_value(Precision::Exact(ScalarValue::Float32(Some(1.1))))
1234 .with_min_value(Precision::Exact(ScalarValue::Float32(Some(0.1))))
1235 .with_sum_value(Precision::Exact(ScalarValue::Float32(Some(42.0)))),
1236 );
1237
1238 let right = Statistics::default()
1239 .with_num_rows(Precision::Exact(7))
1240 .with_total_byte_size(Precision::Exact(29))
1241 .add_column_statistics(
1242 ColumnStatistics::new_unknown()
1243 .with_distinct_count(Precision::Exact(3))
1244 .with_min_value(Precision::Exact(ScalarValue::Int64(Some(1))))
1245 .with_max_value(Precision::Exact(ScalarValue::Int64(Some(34))))
1246 .with_sum_value(Precision::Exact(ScalarValue::Int64(Some(42))))
1247 .with_null_count(Precision::Exact(1)),
1248 )
1249 .add_column_statistics(
1250 ColumnStatistics::new_unknown()
1251 .with_distinct_count(Precision::Exact(3))
1252 .with_min_value(Precision::Exact(ScalarValue::from("b")))
1253 .with_max_value(Precision::Exact(ScalarValue::from("z"))),
1254 )
1255 .add_column_statistics(ColumnStatistics::new_unknown());
1256
1257 let expected = Statistics::default()
1258 .with_num_rows(Precision::Exact(12))
1259 .with_total_byte_size(Precision::Exact(52))
1260 .add_column_statistics(
1261 ColumnStatistics::new_unknown()
1262 .with_distinct_count(Precision::Inexact(6))
1263 .with_min_value(Precision::Exact(ScalarValue::Int64(Some(-4))))
1264 .with_max_value(Precision::Exact(ScalarValue::Int64(Some(34))))
1265 .with_sum_value(Precision::Exact(ScalarValue::Int64(Some(84))))
1266 .with_null_count(Precision::Exact(1)),
1267 )
1268 .add_column_statistics(
1269 ColumnStatistics::new_unknown()
1270 .with_distinct_count(Precision::Inexact(5))
1271 .with_min_value(Precision::Exact(ScalarValue::from("a")))
1272 .with_max_value(Precision::Exact(ScalarValue::from("z"))),
1273 )
1274 .add_column_statistics(ColumnStatistics::new_unknown());
1275
1276 (schema, left, right, expected)
1277 }
1278
1279 #[test]
1280 fn test_union_partition_statistics_uses_shared_statistics_merge() -> Result<()> {
1281 let (schema, left, right, expected) = stats_merge_inputs();
1282
1283 let left: Arc<dyn ExecutionPlan> =
1284 Arc::new(StatisticsExec::new(left, schema.as_ref().clone()));
1285 let right: Arc<dyn ExecutionPlan> =
1286 Arc::new(StatisticsExec::new(right, schema.as_ref().clone()));
1287
1288 let union = UnionExec::try_new(vec![left, right])?;
1289 let stats =
1290 StatisticsContext::new().compute(union.as_ref(), &StatisticsArgs::new())?;
1291
1292 assert_eq!(stats.as_ref(), &expected);
1293 Ok(())
1294 }
1295
1296 #[test]
1297 fn test_union_partition_statistics_uses_shared_statistics_merge_multicolumn()
1298 -> Result<()> {
1299 let (schema, left, right, expected) = stats_merge_multicolumn_inputs();
1300
1301 let left: Arc<dyn ExecutionPlan> =
1302 Arc::new(StatisticsExec::new(left, schema.as_ref().clone()));
1303 let right: Arc<dyn ExecutionPlan> =
1304 Arc::new(StatisticsExec::new(right, schema.as_ref().clone()));
1305
1306 let union = UnionExec::try_new(vec![left, right])?;
1307 let stats =
1308 StatisticsContext::new().compute(union.as_ref(), &StatisticsArgs::new())?;
1309
1310 assert_eq!(stats.as_ref(), &expected);
1311 Ok(())
1312 }
1313
1314 #[test]
1315 fn test_union_partition_statistics_with_mismatched_nullability() -> Result<()> {
1316 let (_, left, right, expected) = stats_merge_inputs();
1323
1324 let expected = expected.with_total_byte_size(Precision::Exact(49));
1332
1333 let non_nullable_schema =
1334 Schema::new(vec![Field::new("a", DataType::UInt32, false)]);
1335 let nullable_schema = Schema::new(vec![Field::new("a", DataType::UInt32, true)]);
1336
1337 let left: Arc<dyn ExecutionPlan> =
1338 Arc::new(StatisticsExec::new(left, non_nullable_schema));
1339 let right: Arc<dyn ExecutionPlan> =
1340 Arc::new(StatisticsExec::new(right, nullable_schema));
1341
1342 let union = UnionExec::try_new(vec![left, right])?;
1343 let stats =
1344 StatisticsContext::new().compute(union.as_ref(), &StatisticsArgs::new())?;
1345
1346 assert_eq!(stats.as_ref(), &expected);
1347 Ok(())
1348 }
1349
1350 #[tokio::test]
1351 async fn test_coerce_schema_no_op_when_already_matching() -> Result<()> {
1352 let schema_not_null =
1353 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1354 let input: Arc<dyn ExecutionPlan> =
1355 TestMemoryExec::try_new_exec(&[vec![]], Arc::clone(&schema_not_null), None)?;
1356
1357 let coerced = coerce_schema(Arc::clone(&input), &schema_not_null)?;
1358 assert!(Arc::ptr_eq(&coerced, &input));
1359
1360 Ok(())
1361 }
1362
1363 #[tokio::test]
1364 async fn test_coerce_schema_casts_only_nullability() -> Result<()> {
1365 let schema_not_null =
1369 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1370 let batch_not_null = RecordBatch::try_new(
1371 Arc::clone(&schema_not_null),
1372 vec![Arc::new(arrow::array::Int32Array::from(vec![1, 2]))],
1373 )?;
1374 let input: Arc<dyn ExecutionPlan> = TestMemoryExec::try_new_exec(
1375 &[vec![batch_not_null]],
1376 Arc::clone(&schema_not_null),
1377 None,
1378 )?;
1379
1380 let nullable_schema =
1381 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
1382 let coerced = coerce_schema(Arc::clone(&input), &nullable_schema)?;
1383 assert_eq!(&coerced.schema(), &nullable_schema);
1384 let plan_str = crate::displayable(coerced.as_ref())
1385 .indent(true)
1386 .to_string();
1387 assert!(
1388 plan_str.contains("CAST"),
1389 "expected a CAST in the coerced plan:\n{plan_str}"
1390 );
1391
1392 let task_ctx = Arc::new(TaskContext::default());
1393 let batches = collect(coerced, task_ctx).await?;
1394 assert_eq!(batches.len(), 1);
1395 assert_eq!(batches[0].schema(), nullable_schema);
1396
1397 Ok(())
1398 }
1399
1400 #[test]
1401 fn test_coerce_schema_rejects_genuine_type_mismatch() -> Result<()> {
1402 let schema_int =
1403 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1404 let input: Arc<dyn ExecutionPlan> =
1405 TestMemoryExec::try_new_exec(&[vec![]], Arc::clone(&schema_int), None)?;
1406
1407 let schema_utf8 =
1408 Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)]));
1409 let err = coerce_schema(input, &schema_utf8).unwrap_err();
1410 assert!(err.to_string().contains("same data type per column"));
1411
1412 Ok(())
1413 }
1414
1415 #[test]
1416 fn test_interleave_partition_statistics_uses_shared_statistics_merge() -> Result<()> {
1417 let (schema, left, right, expected) = stats_merge_inputs();
1418 let hash_expr = vec![col("a", schema.as_ref())?];
1419
1420 let left: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1421 Arc::new(StatisticsExec::new(left, schema.as_ref().clone())),
1422 Partitioning::Hash(hash_expr.clone(), 2),
1423 )?);
1424 let right: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1425 Arc::new(StatisticsExec::new(right, schema.as_ref().clone())),
1426 Partitioning::Hash(hash_expr, 2),
1427 )?);
1428
1429 let interleave = InterleaveExec::try_new(vec![left, right])?;
1430 let stats =
1431 StatisticsContext::new().compute(&interleave, &StatisticsArgs::new())?;
1432
1433 assert_eq!(stats.as_ref(), &expected);
1434 Ok(())
1435 }
1436
1437 #[test]
1438 fn test_interleave_partition_statistics_for_partition_uses_shared_statistics_merge()
1439 -> Result<()> {
1440 let (schema, left, right, _) = stats_merge_inputs();
1441 let hash_expr = vec![col("a", schema.as_ref())?];
1442
1443 let left: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1444 Arc::new(StatisticsExec::new(left, schema.as_ref().clone())),
1445 Partitioning::Hash(hash_expr.clone(), 2),
1446 )?);
1447 let right: Arc<dyn ExecutionPlan> = Arc::new(RepartitionExec::try_new(
1448 Arc::new(StatisticsExec::new(right, schema.as_ref().clone())),
1449 Partitioning::Hash(hash_expr, 2),
1450 )?);
1451
1452 let interleave = InterleaveExec::try_new(vec![left, right])?;
1453 let stats = StatisticsContext::new()
1454 .compute(&interleave, &StatisticsArgs::new().with_partition(Some(0)))?;
1455
1456 let expected = Statistics::default()
1457 .with_num_rows(Precision::Inexact(5))
1458 .with_total_byte_size(Precision::Inexact(25))
1459 .add_column_statistics(ColumnStatistics::new_unknown());
1460
1461 assert_eq!(stats.as_ref(), &expected);
1462 Ok(())
1463 }
1464
1465 #[tokio::test]
1466 async fn test_union_equivalence_properties() -> Result<()> {
1467 let schema = create_test_schema()?;
1468 let col_a = &col("a", &schema)?;
1469 let col_b = &col("b", &schema)?;
1470 let col_c = &col("c", &schema)?;
1471 let col_d = &col("d", &schema)?;
1472 let col_e = &col("e", &schema)?;
1473 let col_f = &col("f", &schema)?;
1474 let options = SortOptions::default();
1475 let test_cases = [
1476 (
1478 vec![
1480 vec![(col_a, options), (col_b, options), (col_f, options)],
1482 ],
1483 vec![
1485 vec![(col_a, options), (col_b, options), (col_c, options)],
1487 vec![(col_a, options), (col_b, options), (col_f, options)],
1489 ],
1490 vec![
1492 vec![(col_a, options), (col_b, options), (col_f, options)],
1494 ],
1495 ),
1496 (
1498 vec![
1500 vec![(col_a, options), (col_b, options), (col_f, options)],
1502 vec![(col_d, options)],
1504 ],
1505 vec![
1507 vec![(col_a, options), (col_b, options), (col_c, options)],
1509 vec![(col_e, options)],
1511 ],
1512 vec![
1514 vec![(col_a, options), (col_b, options)],
1516 ],
1517 ),
1518 ];
1519
1520 for (
1521 test_idx,
1522 (first_child_orderings, second_child_orderings, union_orderings),
1523 ) in test_cases.iter().enumerate()
1524 {
1525 let first_orderings = convert_to_orderings(first_child_orderings);
1526 let second_orderings = convert_to_orderings(second_child_orderings);
1527 let union_expected_orderings = convert_to_orderings(union_orderings);
1528 let child1_exec = TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?
1529 .try_with_sort_information(first_orderings)?;
1530 let child1 = Arc::new(child1_exec);
1531 let child1 = Arc::new(TestMemoryExec::update_cache(&child1));
1532 let child2_exec = TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?
1533 .try_with_sort_information(second_orderings)?;
1534 let child2 = Arc::new(child2_exec);
1535 let child2 = Arc::new(TestMemoryExec::update_cache(&child2));
1536
1537 let mut union_expected_eq = EquivalenceProperties::new(Arc::clone(&schema));
1538 union_expected_eq.add_orderings(union_expected_orderings);
1539
1540 let union: Arc<dyn ExecutionPlan> = UnionExec::try_new(vec![child1, child2])?;
1541 let union_eq_properties = union.properties().equivalence_properties();
1542 let err_msg = format!(
1543 "Error in test id: {:?}, test case: {:?}",
1544 test_idx, test_cases[test_idx]
1545 );
1546 assert_eq_properties_same(union_eq_properties, &union_expected_eq, err_msg);
1547 }
1548 Ok(())
1549 }
1550
1551 fn assert_eq_properties_same(
1552 lhs: &EquivalenceProperties,
1553 rhs: &EquivalenceProperties,
1554 err_msg: String,
1555 ) {
1556 let lhs_orderings = lhs.oeq_class();
1558 let rhs_orderings = rhs.oeq_class();
1559 assert_eq!(lhs_orderings.len(), rhs_orderings.len(), "{err_msg}");
1560 for rhs_ordering in rhs_orderings.iter() {
1561 assert!(lhs_orderings.contains(rhs_ordering), "{}", err_msg);
1562 }
1563 }
1564
1565 #[test]
1566 fn test_union_empty_inputs() {
1567 let result = UnionExec::try_new(vec![]);
1569 assert!(
1570 result
1571 .unwrap_err()
1572 .to_string()
1573 .contains("UnionExec requires at least one input")
1574 );
1575 }
1576
1577 #[test]
1578 fn test_union_schema_empty_inputs() {
1579 let result = union_schema(&[]);
1581 assert!(
1582 result
1583 .unwrap_err()
1584 .to_string()
1585 .contains("Cannot create union schema from empty inputs")
1586 );
1587 }
1588
1589 #[test]
1590 fn test_union_single_input() -> Result<()> {
1591 let schema = create_test_schema()?;
1593 let memory_exec: Arc<dyn ExecutionPlan> =
1594 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
1595 let memory_exec_clone = Arc::clone(&memory_exec);
1596 let result = UnionExec::try_new(vec![memory_exec])?;
1597
1598 assert_eq!(result.schema(), schema);
1600 assert!(Arc::ptr_eq(&result, &memory_exec_clone));
1602
1603 Ok(())
1604 }
1605
1606 #[test]
1607 fn test_union_schema_multiple_inputs() -> Result<()> {
1608 let schema = create_test_schema()?;
1610 let memory_exec1 =
1611 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
1612 let memory_exec2 =
1613 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
1614
1615 let union_plan = UnionExec::try_new(vec![memory_exec1, memory_exec2])?;
1616
1617 let union = union_plan
1619 .downcast_ref::<UnionExec>()
1620 .expect("Expected UnionExec");
1621
1622 assert_eq!(union.schema(), schema);
1624 assert_eq!(union.inputs().len(), 2);
1626
1627 Ok(())
1628 }
1629
1630 #[test]
1631 fn test_union_schema_mismatch() {
1632 let schema = create_test_schema().unwrap();
1634 let schema2 = create_test_schema2().unwrap();
1635 let memory_exec1 =
1636 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None).unwrap());
1637 let memory_exec2 =
1638 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema2), None).unwrap());
1639
1640 let result = UnionExec::try_new(vec![memory_exec1, memory_exec2]);
1641 assert!(result.is_err());
1642 assert!(
1643 result.unwrap_err().to_string().contains(
1644 "UnionExec/InterleaveExec requires all inputs to have the same number of fields"
1645 )
1646 );
1647 }
1648
1649 fn make_hash_exec(
1650 schema: &SchemaRef,
1651 hash_cols: Vec<&str>,
1652 buckets: usize,
1653 ) -> Result<Arc<dyn ExecutionPlan>> {
1654 let exprs = hash_cols
1655 .iter()
1656 .map(|c| col(c, schema))
1657 .collect::<Result<Vec<_>>>()?;
1658 let base = Arc::new(TestMemoryExec::try_new(&[], Arc::clone(schema), None)?);
1659 Ok(Arc::new(RepartitionExec::try_new(
1660 base,
1661 Partitioning::Hash(exprs, buckets),
1662 )?))
1663 }
1664
1665 fn make_range_exec(
1666 schema: &SchemaRef,
1667 split_values: Vec<i32>,
1668 sort_options: SortOptions,
1669 ) -> Result<Arc<dyn ExecutionPlan>> {
1670 let sort_expr =
1671 PhysicalSortExpr::new(col(schema.field(0).name(), schema)?, sort_options);
1672 let ordering = LexOrdering::new(vec![sort_expr]).unwrap();
1673 let split_points = split_values
1674 .into_iter()
1675 .map(|v| SplitPoint::new(vec![ScalarValue::Int32(Some(v))]))
1676 .collect();
1677 let base = Arc::new(TestMemoryExec::try_new(&[], Arc::clone(schema), None)?);
1678 Ok(Arc::new(RepartitionExec::try_new(
1679 base,
1680 Partitioning::Range(RangePartitioning::try_new(ordering, split_points)?),
1681 )?))
1682 }
1683
1684 #[test]
1685 fn test_can_interleave_matrix() -> Result<()> {
1686 let name_column = "name";
1687 let age_column = "age";
1688 let schema = Arc::new(Schema::new(vec![
1689 Field::new(name_column, DataType::Int32, true),
1690 Field::new(age_column, DataType::Int32, true),
1691 ]));
1692
1693 let ascending = SortOptions {
1694 descending: false,
1695 nulls_first: false,
1696 };
1697 struct Case {
1698 inputs: Vec<Arc<dyn ExecutionPlan>>,
1699 expected: bool,
1700 label: &'static str,
1701 }
1702
1703 let cases = vec![
1704 Case {
1706 label: "matching hash on single column",
1707 expected: true,
1708 inputs: vec![
1709 make_hash_exec(&schema, vec![name_column], 3)?,
1710 make_hash_exec(&schema, vec![name_column], 3)?,
1711 ],
1712 },
1713 Case {
1714 label: "matching hash on multiple columns",
1715 expected: true,
1716 inputs: vec![
1717 make_hash_exec(&schema, vec![name_column, age_column], 3)?,
1718 make_hash_exec(&schema, vec![name_column, age_column], 3)?,
1719 ],
1720 },
1721 Case {
1722 label: "matching range same splits and order",
1723 expected: true,
1724 inputs: vec![
1725 make_range_exec(&schema, vec![10, 20], ascending)?,
1726 make_range_exec(&schema, vec![10, 20], ascending)?,
1727 ],
1728 },
1729 Case {
1731 label: "subset range partition",
1732 expected: false,
1733 inputs: vec![
1734 make_range_exec(&schema, vec![10, 20], ascending)?,
1735 make_range_exec(&schema, vec![10, 15], ascending)?,
1736 ],
1737 },
1738 Case {
1739 label: "range different split points",
1740 expected: false,
1741 inputs: vec![
1742 make_range_exec(&schema, vec![10, 20], ascending)?,
1743 make_range_exec(&schema, vec![10, 30], ascending)?,
1744 ],
1745 },
1746 Case {
1747 label: "mixed range and hash",
1748 expected: false,
1749 inputs: vec![
1750 make_range_exec(&schema, vec![10, 20], ascending)?,
1751 make_hash_exec(&schema, vec![name_column], 3)?,
1752 ],
1753 },
1754 ];
1755
1756 for case in cases {
1757 assert_eq!(
1758 can_interleave(case.inputs.iter()),
1759 case.expected,
1760 "{}",
1761 case.label
1762 );
1763 }
1764 Ok(())
1765 }
1766
1767 #[test]
1768 fn test_union_cardinality_effect() -> Result<()> {
1769 let schema = create_test_schema()?;
1770 let input1: Arc<dyn ExecutionPlan> =
1771 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
1772 let input2: Arc<dyn ExecutionPlan> =
1773 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
1774
1775 let union = UnionExec::try_new(vec![input1, input2])?;
1776 let union = union
1777 .downcast_ref::<UnionExec>()
1778 .expect("expected UnionExec for multiple inputs");
1779
1780 assert!(matches!(
1781 union.cardinality_effect(),
1782 CardinalityEffect::GreaterEqual
1783 ));
1784 Ok(())
1785 }
1786}