1use crate::{
21 EquivalenceProperties, PhysicalExpr, equivalence::ProjectionMapping,
22 expressions::UnKnownColumn, physical_exprs_contains, physical_exprs_equal,
23};
24pub use datafusion_common::SplitPoint;
25use datafusion_common::{Result, validate_range_split_points};
26use datafusion_physical_expr_common::physical_expr::format_physical_expr_list;
27use datafusion_physical_expr_common::sort_expr::{LexOrdering, PhysicalSortExpr};
28#[cfg(feature = "proto")]
29use datafusion_physical_expr_common::sort_expr::{
30 sort_exprs_try_from_proto, sort_exprs_try_to_proto,
31};
32use std::fmt;
33use std::fmt::Display;
34use std::sync::Arc;
35
36#[derive(Debug, Clone)]
121pub enum Partitioning {
122 RoundRobinBatch(usize),
124 Hash(Vec<Arc<dyn PhysicalExpr>>, usize),
127 Range(RangePartitioning),
129 UnknownPartitioning(usize),
131}
132
133impl Display for Partitioning {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 match self {
136 Partitioning::RoundRobinBatch(size) => write!(f, "RoundRobinBatch({size})"),
137 Partitioning::Hash(phy_exprs, size) => {
138 let phy_exprs_str = phy_exprs
139 .iter()
140 .map(|e| format!("{e}"))
141 .collect::<Vec<String>>()
142 .join(", ");
143 write!(f, "Hash([{phy_exprs_str}], {size})")
144 }
145 Partitioning::Range(range) => write!(f, "{range}"),
146 Partitioning::UnknownPartitioning(size) => {
147 write!(f, "UnknownPartitioning({size})")
148 }
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq)]
204pub struct RangePartitioning {
205 ordering: LexOrdering,
207 split_points: Vec<SplitPoint>,
209}
210
211impl RangePartitioning {
212 pub fn new(ordering: LexOrdering, split_points: Vec<SplitPoint>) -> Self {
217 Self {
218 ordering,
219 split_points,
220 }
221 }
222
223 pub fn try_new(ordering: LexOrdering, split_points: Vec<SplitPoint>) -> Result<Self> {
226 validate_range_split_points(
227 &split_points,
228 &ordering
229 .iter()
230 .map(|sort_expr| sort_expr.options)
231 .collect::<Vec<_>>(),
232 )?;
233 Ok(Self::new(ordering, split_points))
234 }
235
236 pub fn ordering(&self) -> &LexOrdering {
238 &self.ordering
239 }
240
241 pub fn split_points(&self) -> &[SplitPoint] {
243 &self.split_points
244 }
245
246 pub fn partition_count(&self) -> usize {
248 self.split_points.len() + 1
249 }
250
251 fn project(
256 &self,
257 mapping: &ProjectionMapping,
258 input_eq_properties: &EquivalenceProperties,
259 ) -> Option<Self> {
260 let exprs = self
261 .ordering
262 .iter()
263 .map(|sort_expr| Arc::clone(&sort_expr.expr))
264 .collect::<Vec<_>>();
265 let projected_exprs = input_eq_properties
266 .project_expressions(&exprs, mapping)
267 .collect::<Option<Vec<_>>>()?;
268 let sort_exprs = self
269 .ordering
270 .iter()
271 .zip(projected_exprs)
272 .map(|(sort_expr, expr)| PhysicalSortExpr::new(expr, sort_expr.options))
273 .collect::<Vec<_>>();
274 let ordering = LexOrdering::new(sort_exprs)?;
275 if ordering.len() != self.ordering.len() {
276 return None;
277 }
278
279 Some(Self {
280 ordering,
281 split_points: self.split_points.clone(),
282 })
283 }
284}
285
286impl Display for RangePartitioning {
287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 let split_points = format_range_split_points(&self.split_points);
289 write!(
290 f,
291 "Range([{}], [{}], {})",
292 self.ordering,
293 split_points,
294 self.partition_count()
295 )
296 }
297}
298
299fn format_range_split_points(split_points: &[SplitPoint]) -> String {
300 split_points
301 .iter()
302 .map(ToString::to_string)
303 .collect::<Vec<_>>()
304 .join(", ")
305}
306
307fn equivalent_exprs(
308 left: &[Arc<dyn PhysicalExpr>],
309 right: &[Arc<dyn PhysicalExpr>],
310 eq_properties: &EquivalenceProperties,
311) -> bool {
312 if physical_exprs_equal(left, right) {
313 return true;
314 }
315
316 let eq_groups = eq_properties.eq_group();
317 if eq_groups.is_empty() {
318 return false;
319 }
320
321 let normalized_left = normalize_exprs(left, eq_properties);
322 let normalized_right = normalize_exprs(right, eq_properties);
323
324 physical_exprs_equal(&normalized_left, &normalized_right)
325}
326
327fn normalize_exprs(
328 exprs: &[Arc<dyn PhysicalExpr>],
329 eq_properties: &EquivalenceProperties,
330) -> Vec<Arc<dyn PhysicalExpr>> {
331 let eq_groups = eq_properties.eq_group();
332 exprs
333 .iter()
334 .map(|expr| eq_groups.normalize_expr(Arc::clone(expr)))
335 .collect()
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum PartitioningSatisfaction {
341 NotSatisfied,
343 Exact,
345 Subset,
347}
348
349impl PartitioningSatisfaction {
350 pub fn is_satisfied(&self) -> bool {
351 matches!(self, Self::Exact | Self::Subset)
352 }
353
354 pub fn is_subset(&self) -> bool {
355 *self == Self::Subset
356 }
357}
358
359impl Partitioning {
360 pub fn partition_count(&self) -> usize {
362 use Partitioning::*;
363 match self {
364 RoundRobinBatch(n) | Hash(_, n) | UnknownPartitioning(n) => *n,
365 Range(range) => range.partition_count(),
366 }
367 }
368
369 fn is_subset_partitioning(
373 subset_exprs: &[Arc<dyn PhysicalExpr>],
374 superset_exprs: &[Arc<dyn PhysicalExpr>],
375 ) -> bool {
376 if subset_exprs.is_empty() || subset_exprs.len() >= superset_exprs.len() {
378 return false;
379 }
380
381 subset_exprs
382 .iter()
383 .all(|subset_expr| physical_exprs_contains(superset_exprs, subset_expr))
384 }
385
386 #[deprecated(since = "52.0.0", note = "Use satisfaction instead")]
387 pub fn satisfy(
388 &self,
389 required: &Distribution,
390 eq_properties: &EquivalenceProperties,
391 ) -> bool {
392 self.satisfaction(required, eq_properties, false)
393 == PartitioningSatisfaction::Exact
394 }
395
396 #[expect(
399 deprecated,
400 reason = "HashPartitioned is accepted during the KeyPartitioned migration"
401 )]
402 pub fn satisfaction(
403 &self,
404 required: &Distribution,
405 eq_properties: &EquivalenceProperties,
406 allow_subset: bool,
407 ) -> PartitioningSatisfaction {
408 match required {
409 Distribution::UnspecifiedDistribution => PartitioningSatisfaction::Exact,
410 Distribution::SinglePartition if self.partition_count() == 1 => {
411 PartitioningSatisfaction::Exact
412 }
413 Distribution::HashPartitioned(_) | Distribution::KeyPartitioned(_)
415 if self.partition_count() == 1 =>
416 {
417 PartitioningSatisfaction::Exact
418 }
419 Distribution::HashPartitioned(required_exprs)
420 | Distribution::KeyPartitioned(required_exprs) => match self {
421 Partitioning::Hash(partition_exprs, _) => Self::key_satisfaction(
425 partition_exprs,
426 required_exprs,
427 eq_properties,
428 allow_subset,
429 ),
430 Partitioning::Range(range) => {
431 let partition_exprs = range
432 .ordering()
433 .iter()
434 .map(|sort_expr| Arc::clone(&sort_expr.expr))
435 .collect::<Vec<_>>();
436 Self::key_satisfaction(
437 &partition_exprs,
438 required_exprs,
439 eq_properties,
440 allow_subset,
441 )
442 }
443 Partitioning::RoundRobinBatch(_)
444 | Partitioning::UnknownPartitioning(_) => {
445 PartitioningSatisfaction::NotSatisfied
446 }
447 },
448 Distribution::SinglePartition => PartitioningSatisfaction::NotSatisfied,
449 }
450 }
451
452 fn key_satisfaction(
453 partition_exprs: &[Arc<dyn PhysicalExpr>],
454 required_exprs: &[Arc<dyn PhysicalExpr>],
455 eq_properties: &EquivalenceProperties,
456 allow_subset: bool,
457 ) -> PartitioningSatisfaction {
458 if partition_exprs.is_empty() || required_exprs.is_empty() {
459 return PartitioningSatisfaction::NotSatisfied;
460 }
461
462 if equivalent_exprs(required_exprs, partition_exprs, eq_properties) {
463 return PartitioningSatisfaction::Exact;
464 }
465
466 let eq_groups = eq_properties.eq_group();
467 if !eq_groups.is_empty() {
468 if allow_subset {
469 let normalized_partition_exprs =
470 normalize_exprs(partition_exprs, eq_properties);
471 let normalized_required_exprs =
472 normalize_exprs(required_exprs, eq_properties);
473 if Self::is_subset_partitioning(
474 &normalized_partition_exprs,
475 &normalized_required_exprs,
476 ) {
477 return PartitioningSatisfaction::Subset;
478 }
479 }
480 } else if allow_subset
481 && Self::is_subset_partitioning(partition_exprs, required_exprs)
482 {
483 return PartitioningSatisfaction::Subset;
484 }
485
486 PartitioningSatisfaction::NotSatisfied
487 }
488
489 pub fn project(
491 &self,
492 mapping: &ProjectionMapping,
493 input_eq_properties: &EquivalenceProperties,
494 ) -> Self {
495 match self {
496 Partitioning::Hash(exprs, part) => {
497 let normalized_exprs = input_eq_properties
498 .project_expressions(exprs, mapping)
499 .zip(exprs)
500 .map(|(proj_expr, expr)| {
501 proj_expr.unwrap_or_else(|| {
502 Arc::new(UnKnownColumn::new(&expr.to_string()))
503 })
504 })
505 .collect();
506 Partitioning::Hash(normalized_exprs, *part)
507 }
508 Partitioning::Range(range) => {
509 if let Some(projected) = range.project(mapping, input_eq_properties) {
510 Partitioning::Range(projected)
511 } else {
512 Partitioning::UnknownPartitioning(range.partition_count())
513 }
514 }
515 Partitioning::RoundRobinBatch(_) | Partitioning::UnknownPartitioning(_) => {
516 self.clone()
517 }
518 }
519 }
520}
521
522#[cfg(feature = "proto")]
533impl Partitioning {
534 pub fn try_to_proto(
536 &self,
537 ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
538 ) -> Result<datafusion_proto_models::protobuf::Partitioning> {
539 use datafusion_proto_models::protobuf;
540
541 let partition_method = match self {
542 Partitioning::RoundRobinBatch(n) => {
543 protobuf::partitioning::PartitionMethod::RoundRobin(wire_partition_count(
544 *n,
545 )?)
546 }
547 Partitioning::Hash(exprs, n) => {
548 protobuf::partitioning::PartitionMethod::Hash(
549 protobuf::PhysicalHashRepartition {
550 hash_expr: ctx.encode_children_expressions(exprs)?,
551 partition_count: wire_partition_count(*n)?,
552 },
553 )
554 }
555 Partitioning::Range(range) => {
556 let sort_expr = sort_exprs_try_to_proto(range.ordering().iter(), ctx)?;
557 let split_point = range
558 .split_points()
559 .iter()
560 .map(|split_point| {
561 let value = split_point
562 .values()
563 .iter()
564 .map(|value| value.try_into().map_err(Into::into))
565 .collect::<Result<Vec<_>>>()?;
566 Ok(protobuf::PhysicalRangeSplitPoint { value })
567 })
568 .collect::<Result<Vec<_>>>()?;
569 protobuf::partitioning::PartitionMethod::Range(
570 protobuf::PhysicalRangePartitioning {
571 sort_expr,
572 split_point,
573 },
574 )
575 }
576 Partitioning::UnknownPartitioning(n) => {
577 protobuf::partitioning::PartitionMethod::Unknown(wire_partition_count(
578 *n,
579 )?)
580 }
581 };
582 Ok(protobuf::Partitioning {
583 partition_method: Some(partition_method),
584 })
585 }
586
587 pub fn try_from_proto(
593 node: &datafusion_proto_models::protobuf::Partitioning,
594 ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
595 ) -> Result<Option<Self>> {
596 use datafusion_common::{ScalarValue, internal_datafusion_err, internal_err};
597 use datafusion_proto_models::protobuf;
598
599 let Some(partition_method) = node.partition_method.as_ref() else {
600 return Ok(None);
601 };
602 let partitioning = match partition_method {
603 protobuf::partitioning::PartitionMethod::RoundRobin(n) => {
604 Partitioning::RoundRobinBatch(partition_count(*n)?)
605 }
606 protobuf::partitioning::PartitionMethod::Hash(hash) => {
607 let exprs = hash
608 .hash_expr
609 .iter()
610 .map(|expr| ctx.decode(expr))
611 .collect::<Result<Vec<_>>>()?;
612 Partitioning::Hash(exprs, partition_count(hash.partition_count)?)
613 }
614 protobuf::partitioning::PartitionMethod::Unknown(n) => {
615 Partitioning::UnknownPartitioning(partition_count(*n)?)
616 }
617 protobuf::partitioning::PartitionMethod::Range(range) => {
618 let sort_exprs = sort_exprs_try_from_proto(&range.sort_expr, ctx)?;
619 let sort_expr_count = sort_exprs.len();
620 let ordering = LexOrdering::new(sort_exprs).ok_or_else(|| {
621 internal_datafusion_err!(
622 "Range partitioning requires non-empty ordering"
623 )
624 })?;
625 if ordering.len() != sort_expr_count {
626 return internal_err!(
627 "Range partitioning ordering must not contain duplicate expressions"
628 );
629 }
630 let split_points = range
631 .split_point
632 .iter()
633 .map(|split_point| {
634 let values = split_point
635 .value
636 .iter()
637 .map(|value| ScalarValue::try_from(value).map_err(Into::into))
638 .collect::<Result<Vec<_>>>()?;
639 Ok(SplitPoint::new(values))
640 })
641 .collect::<Result<Vec<_>>>()?;
642 Partitioning::Range(RangePartitioning::try_new(ordering, split_points)?)
643 }
644 };
645 Ok(Some(partitioning))
646 }
647}
648
649#[cfg(feature = "proto")]
651fn partition_count(count: u64) -> Result<usize> {
652 usize::try_from(count).map_err(|_| {
653 datafusion_common::internal_datafusion_err!(
654 "Partition count {count} exceeds usize::MAX"
655 )
656 })
657}
658
659#[cfg(feature = "proto")]
664fn wire_partition_count(count: usize) -> Result<u64> {
665 u64::try_from(count).map_err(|_| {
666 datafusion_common::internal_datafusion_err!(
667 "Partition count {count} exceeds u64::MAX"
668 )
669 })
670}
671
672impl PartialEq for Partitioning {
673 fn eq(&self, other: &Partitioning) -> bool {
674 match (self, other) {
675 (
676 Partitioning::RoundRobinBatch(count1),
677 Partitioning::RoundRobinBatch(count2),
678 ) if count1 == count2 => true,
679 (Partitioning::Hash(exprs1, count1), Partitioning::Hash(exprs2, count2))
680 if physical_exprs_equal(exprs1, exprs2) && (count1 == count2) =>
681 {
682 true
683 }
684 (Partitioning::Range(left), Partitioning::Range(right)) => left == right,
685 _ => false,
686 }
687 }
688}
689
690#[derive(Debug, Clone)]
693pub enum Distribution {
694 UnspecifiedDistribution,
696 SinglePartition,
698 #[deprecated(since = "55.0.0", note = "Use Distribution::KeyPartitioned")]
701 HashPartitioned(Vec<Arc<dyn PhysicalExpr>>),
702 KeyPartitioned(Vec<Arc<dyn PhysicalExpr>>),
705}
706
707#[expect(
708 deprecated,
709 reason = "HashPartitioned is accepted during the KeyPartitioned migration"
710)]
711impl Distribution {
712 pub fn create_partitioning(self, partition_count: usize) -> Partitioning {
714 match self {
715 Distribution::UnspecifiedDistribution => {
716 Partitioning::UnknownPartitioning(partition_count)
717 }
718 Distribution::SinglePartition => Partitioning::UnknownPartitioning(1),
719 Distribution::HashPartitioned(expr) | Distribution::KeyPartitioned(expr) => {
720 Partitioning::Hash(expr, partition_count)
721 }
722 }
723 }
724}
725
726#[expect(
727 deprecated,
728 reason = "HashPartitioned display is preserved during the KeyPartitioned migration"
729)]
730impl Display for Distribution {
731 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
732 match self {
733 Distribution::UnspecifiedDistribution => write!(f, "Unspecified"),
734 Distribution::SinglePartition => write!(f, "SinglePartition"),
735 Distribution::HashPartitioned(exprs) => {
736 write!(f, "HashPartitioned[{}])", format_physical_expr_list(exprs))
737 }
738 Distribution::KeyPartitioned(exprs) => {
739 write!(f, "KeyPartitioned[{}])", format_physical_expr_list(exprs))
740 }
741 }
742 }
743}
744
745#[cfg(test)]
746mod tests {
747
748 use super::*;
749 use crate::expressions::Column;
750 use crate::projection::ProjectionTargets;
751
752 use arrow::compute::SortOptions;
753 use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
754 use datafusion_common::{Result, ScalarValue};
755
756 struct PartitioningTestFixture {
757 schema: SchemaRef,
758 cols: Vec<Arc<dyn PhysicalExpr>>,
759 eq_properties: EquivalenceProperties,
760 }
761
762 impl PartitioningTestFixture {
763 fn new(fields: Vec<(&str, DataType)>) -> Result<Self> {
764 let schema = Arc::new(Schema::new(
765 fields
766 .iter()
767 .map(|(name, data_type)| Field::new(*name, data_type.clone(), false))
768 .collect::<Vec<_>>(),
769 ));
770 let cols = fields
771 .iter()
772 .map(|(name, _)| {
773 Ok(Arc::new(Column::new_with_schema(name, &schema)?)
774 as Arc<dyn PhysicalExpr>)
775 })
776 .collect::<Result<_>>()?;
777 let eq_properties = EquivalenceProperties::new(Arc::clone(&schema));
778
779 Ok(Self {
780 schema,
781 cols,
782 eq_properties,
783 })
784 }
785
786 fn int64(names: &[&str]) -> Result<Self> {
787 Self::new(names.iter().map(|name| (*name, DataType::Int64)).collect())
788 }
789
790 fn col(&self, index: usize) -> Arc<dyn PhysicalExpr> {
791 Arc::clone(&self.cols[index])
792 }
793
794 fn cols(
795 &self,
796 indices: impl IntoIterator<Item = usize>,
797 ) -> Vec<Arc<dyn PhysicalExpr>> {
798 indices.into_iter().map(|index| self.col(index)).collect()
799 }
800
801 fn hash_partitioning(
802 &self,
803 indices: impl IntoIterator<Item = usize>,
804 partition_count: usize,
805 ) -> Partitioning {
806 Partitioning::Hash(self.cols(indices), partition_count)
807 }
808
809 fn key_distribution(
810 &self,
811 indices: impl IntoIterator<Item = usize>,
812 ) -> Distribution {
813 Distribution::KeyPartitioned(self.cols(indices))
814 }
815
816 fn range_sort_expr(
817 &self,
818 index: usize,
819 options: SortOptions,
820 ) -> PhysicalSortExpr {
821 PhysicalSortExpr::new(self.col(index), options)
822 }
823
824 fn range_ordering(
825 &self,
826 indices: impl IntoIterator<Item = usize>,
827 ) -> LexOrdering {
828 LexOrdering::new(
829 indices
830 .into_iter()
831 .map(|index| PhysicalSortExpr::new_default(self.col(index))),
832 )
833 .expect("ordering must not be empty")
834 }
835
836 fn range(
837 &self,
838 indices: impl IntoIterator<Item = usize>,
839 split_points: Vec<SplitPoint>,
840 ) -> RangePartitioning {
841 RangePartitioning::try_new(self.range_ordering(indices), split_points)
842 .expect("test range partitioning should be valid")
843 }
844
845 fn range_partitioning(
846 &self,
847 indices: impl IntoIterator<Item = usize>,
848 split_points: Vec<SplitPoint>,
849 ) -> Partitioning {
850 Partitioning::Range(self.range(indices, split_points))
851 }
852
853 fn range_partitioning_with_ordering(
854 &self,
855 ordering: LexOrdering,
856 split_points: Vec<SplitPoint>,
857 ) -> Partitioning {
858 Partitioning::Range(
859 RangePartitioning::try_new(ordering, split_points)
860 .expect("test range partitioning should be valid"),
861 )
862 }
863 }
864
865 fn assert_satisfaction(
866 desc: &str,
867 partitioning: &Partitioning,
868 required: &Distribution,
869 eq_properties: &EquivalenceProperties,
870 expected_with_subset: PartitioningSatisfaction,
871 expected_without_subset: PartitioningSatisfaction,
872 ) {
873 assert_eq!(
874 partitioning.satisfaction(required, eq_properties, true),
875 expected_with_subset,
876 "Failed for {desc} with subset enabled"
877 );
878 assert_eq!(
879 partitioning.satisfaction(required, eq_properties, false),
880 expected_without_subset,
881 "Failed for {desc} with subset disabled"
882 );
883 }
884
885 #[test]
886 #[expect(
887 deprecated,
888 reason = "test intentionally covers deprecated HashPartitioned compatibility"
889 )]
890 fn partitioning_satisfy_distribution() -> Result<()> {
891 let fixture = PartitioningTestFixture::new(vec![
892 ("column_1", DataType::Int64),
893 ("column_2", DataType::Utf8),
894 ])?;
895
896 let distribution_types = vec![
897 Distribution::UnspecifiedDistribution,
898 Distribution::SinglePartition,
899 Distribution::HashPartitioned(fixture.cols([0, 1])),
900 fixture.key_distribution([0, 1]),
901 ];
902
903 let single_partition = Partitioning::UnknownPartitioning(1);
904 let unspecified_partition = Partitioning::UnknownPartitioning(10);
905 let round_robin_partition = Partitioning::RoundRobinBatch(10);
906 let hash_partition1 = fixture.hash_partitioning([0, 1], 10);
907 let hash_partition2 = fixture.hash_partitioning([1, 0], 10);
908
909 for distribution in distribution_types {
910 let result = (
911 single_partition
912 .satisfaction(&distribution, &fixture.eq_properties, true)
913 .is_satisfied(),
914 unspecified_partition
915 .satisfaction(&distribution, &fixture.eq_properties, true)
916 .is_satisfied(),
917 round_robin_partition
918 .satisfaction(&distribution, &fixture.eq_properties, true)
919 .is_satisfied(),
920 hash_partition1
921 .satisfaction(&distribution, &fixture.eq_properties, true)
922 .is_satisfied(),
923 hash_partition2
924 .satisfaction(&distribution, &fixture.eq_properties, true)
925 .is_satisfied(),
926 );
927
928 match distribution {
929 Distribution::UnspecifiedDistribution => {
930 assert_eq!(result, (true, true, true, true, true))
931 }
932 Distribution::SinglePartition => {
933 assert_eq!(result, (true, false, false, false, false))
934 }
935 Distribution::HashPartitioned(_) | Distribution::KeyPartitioned(_) => {
936 assert_eq!(result, (true, false, false, true, false))
937 }
938 }
939 }
940
941 Ok(())
942 }
943
944 #[test]
945 #[expect(
946 deprecated,
947 reason = "test intentionally covers deprecated HashPartitioned compatibility"
948 )]
949 fn deprecated_hash_partitioned_matches_key_partitioned() -> Result<()> {
950 let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
951 let partitioning = fixture.hash_partitioning([0, 1], 4);
952 let hash_distribution = Distribution::HashPartitioned(fixture.cols([0, 1]));
953 let key_distribution = fixture.key_distribution([0, 1]);
954
955 assert_eq!(
956 partitioning.satisfaction(&hash_distribution, &fixture.eq_properties, false),
957 partitioning.satisfaction(&key_distribution, &fixture.eq_properties, false)
958 );
959 assert_eq!(
960 hash_distribution.create_partitioning(4),
961 key_distribution.create_partitioning(4)
962 );
963
964 Ok(())
965 }
966
967 #[test]
968 fn hash_partitioning_key_distribution_satisfaction() -> Result<()> {
969 let fixture = PartitioningTestFixture::int64(&["a", "b", "c"])?;
970 let unknown: Arc<dyn PhysicalExpr> = Arc::new(UnKnownColumn::new("dropped"));
971
972 let test_cases = vec![
973 (
974 "exact: KeyPartitioned([a, b]) satisfied by Hash([a, b])",
975 fixture.hash_partitioning([0, 1], 4),
976 fixture.key_distribution([0, 1]),
977 PartitioningSatisfaction::Exact,
978 PartitioningSatisfaction::Exact,
979 ),
980 (
981 "subset: KeyPartitioned([a, b]) satisfied by Hash([a])",
982 fixture.hash_partitioning([0], 4),
983 fixture.key_distribution([0, 1]),
984 PartitioningSatisfaction::Subset,
985 PartitioningSatisfaction::NotSatisfied,
986 ),
987 (
988 "subset: KeyPartitioned([a, b, c]) satisfied by Hash([b])",
989 fixture.hash_partitioning([1], 4),
990 fixture.key_distribution([0, 1, 2]),
991 PartitioningSatisfaction::Subset,
992 PartitioningSatisfaction::NotSatisfied,
993 ),
994 (
995 "subset reordered: KeyPartitioned([a, b, c]) satisfied by Hash([b, a])",
996 fixture.hash_partitioning([1, 0], 4),
997 fixture.key_distribution([0, 1, 2]),
998 PartitioningSatisfaction::Subset,
999 PartitioningSatisfaction::NotSatisfied,
1000 ),
1001 (
1002 "superset: KeyPartitioned([a]) not satisfied by Hash([a, b])",
1003 fixture.hash_partitioning([0, 1], 4),
1004 fixture.key_distribution([0]),
1005 PartitioningSatisfaction::NotSatisfied,
1006 PartitioningSatisfaction::NotSatisfied,
1007 ),
1008 (
1009 "superset: KeyPartitioned([a, b]) not satisfied by Hash([a, b, c])",
1010 fixture.hash_partitioning([0, 1, 2], 4),
1011 fixture.key_distribution([0, 1]),
1012 PartitioningSatisfaction::NotSatisfied,
1013 PartitioningSatisfaction::NotSatisfied,
1014 ),
1015 (
1016 "partial overlap: KeyPartitioned([a, b]) not satisfied by Hash([a, c])",
1017 fixture.hash_partitioning([0, 2], 4),
1018 fixture.key_distribution([0, 1]),
1019 PartitioningSatisfaction::NotSatisfied,
1020 PartitioningSatisfaction::NotSatisfied,
1021 ),
1022 (
1023 "no overlap: KeyPartitioned([b, c]) not satisfied by Hash([a])",
1024 fixture.hash_partitioning([0], 4),
1025 fixture.key_distribution([1, 2]),
1026 PartitioningSatisfaction::NotSatisfied,
1027 PartitioningSatisfaction::NotSatisfied,
1028 ),
1029 (
1030 "unknown partition expr",
1031 Partitioning::Hash(vec![Arc::clone(&unknown)], 4),
1032 fixture.key_distribution([0, 1]),
1033 PartitioningSatisfaction::NotSatisfied,
1034 PartitioningSatisfaction::NotSatisfied,
1035 ),
1036 (
1037 "unknown required expr",
1038 fixture.hash_partitioning([0, 1], 4),
1039 Distribution::KeyPartitioned(vec![Arc::clone(&unknown)]),
1040 PartitioningSatisfaction::NotSatisfied,
1041 PartitioningSatisfaction::NotSatisfied,
1042 ),
1043 (
1044 "same unknown expr",
1045 Partitioning::Hash(vec![Arc::clone(&unknown)], 4),
1046 Distribution::KeyPartitioned(vec![Arc::clone(&unknown)]),
1047 PartitioningSatisfaction::NotSatisfied,
1048 PartitioningSatisfaction::NotSatisfied,
1049 ),
1050 (
1051 "unknown partition expr is not a valid subset",
1052 Partitioning::Hash(vec![Arc::clone(&unknown)], 4),
1053 Distribution::KeyPartitioned(vec![Arc::clone(&unknown), fixture.col(0)]),
1054 PartitioningSatisfaction::NotSatisfied,
1055 PartitioningSatisfaction::NotSatisfied,
1056 ),
1057 (
1058 "empty hash partitioning",
1059 Partitioning::Hash(vec![], 4),
1060 fixture.key_distribution([0]),
1061 PartitioningSatisfaction::NotSatisfied,
1062 PartitioningSatisfaction::NotSatisfied,
1063 ),
1064 (
1065 "empty key distribution",
1066 fixture.hash_partitioning([0], 4),
1067 Distribution::KeyPartitioned(vec![]),
1068 PartitioningSatisfaction::NotSatisfied,
1069 PartitioningSatisfaction::NotSatisfied,
1070 ),
1071 ];
1072
1073 for (desc, partition, required, expected_with_subset, expected_without_subset) in
1074 test_cases
1075 {
1076 assert_satisfaction(
1077 desc,
1078 &partition,
1079 &required,
1080 &fixture.eq_properties,
1081 expected_with_subset,
1082 expected_without_subset,
1083 );
1084 }
1085
1086 Ok(())
1087 }
1088
1089 fn int_split_point(values: impl IntoIterator<Item = i64>) -> SplitPoint {
1090 SplitPoint::new(
1091 values
1092 .into_iter()
1093 .map(|value| ScalarValue::Int64(Some(value)))
1094 .collect(),
1095 )
1096 }
1097
1098 fn assert_range_try_new_error(
1099 ordering: LexOrdering,
1100 split_points: Vec<SplitPoint>,
1101 expected: &str,
1102 ) {
1103 let error = RangePartitioning::try_new(ordering, split_points)
1104 .unwrap_err()
1105 .to_string();
1106 assert!(error.contains(expected), "{error}");
1107 }
1108
1109 #[test]
1110 fn test_range_partitioning_metadata() -> Result<()> {
1111 let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1112
1113 let range_partitioning =
1114 fixture.range([0], vec![int_split_point([10]), int_split_point([20])]);
1115 assert_eq!(range_partitioning.ordering()[0].to_string(), "a@0 ASC");
1116 assert_eq!(
1117 range_partitioning.split_points(),
1118 &[int_split_point([10]), int_split_point([20])]
1119 );
1120 let partitioning = Partitioning::Range(range_partitioning);
1121
1122 assert_eq!(partitioning.partition_count(), 3);
1123 assert_eq!(
1124 partitioning.to_string(),
1125 "Range([a@0 ASC], [(10), (20)], 3)"
1126 );
1127
1128 Ok(())
1129 }
1130
1131 #[test]
1132 fn test_range_partitioning_try_new_validates_split_points() -> Result<()> {
1133 let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1134 let asc_a = fixture.range_ordering([0]);
1135 let ordering_ab = fixture.range_ordering([0, 1]);
1136
1137 assert_range_try_new_error(
1138 ordering_ab.clone(),
1139 vec![int_split_point([10])],
1140 "split point 0 has width 1, but ordering has width 2",
1141 );
1142
1143 RangePartitioning::try_new(
1144 [fixture.range_sort_expr(0, SortOptions::new(true, false))].into(),
1145 vec![int_split_point([20]), int_split_point([10])],
1146 )?;
1147
1148 assert_range_try_new_error(
1149 asc_a,
1150 vec![int_split_point([20]), int_split_point([10])],
1151 "split points must be strictly ordered",
1152 );
1153
1154 assert_range_try_new_error(
1155 [fixture.range_sort_expr(0, SortOptions::new(false, false))].into(),
1156 vec![
1157 SplitPoint::new(vec![ScalarValue::Int64(None)]),
1158 int_split_point([10]),
1159 ],
1160 "split points must be strictly ordered",
1161 );
1162
1163 RangePartitioning::try_new(
1164 ordering_ab.clone(),
1165 vec![int_split_point([10, 20]), int_split_point([10, 30])],
1166 )?;
1167
1168 assert_range_try_new_error(
1169 ordering_ab,
1170 vec![int_split_point([10, 30]), int_split_point([10, 20])],
1171 "split points must be strictly ordered",
1172 );
1173
1174 Ok(())
1175 }
1176
1177 #[test]
1178 fn test_range_partitioning_project_preserves_or_degrades() -> Result<()> {
1179 let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1180 let range_partitioning = fixture.range_partitioning_with_ordering(
1181 [fixture.range_sort_expr(1, SortOptions::new(true, false))].into(),
1182 vec![int_split_point([10])],
1183 );
1184
1185 let keep_b_mapping = ProjectionMapping::from_indices(&[1], &fixture.schema)?;
1186 let projected =
1187 range_partitioning.project(&keep_b_mapping, &fixture.eq_properties);
1188 assert_eq!(
1189 projected.to_string(),
1190 "Range([b@0 DESC NULLS LAST], [(10)], 2)"
1191 );
1192
1193 let drop_b_mapping = ProjectionMapping::from_indices(&[0], &fixture.schema)?;
1194 let projected =
1195 range_partitioning.project(&drop_b_mapping, &fixture.eq_properties);
1196 let Partitioning::UnknownPartitioning(partition_count) = projected else {
1197 panic!("expected UnknownPartitioning, got {projected:?}");
1198 };
1199 assert_eq!(partition_count, 2);
1200
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn test_range_partitioning_project_degrades_if_ordering_collapses() -> Result<()> {
1206 let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1207 let target: Arc<dyn PhysicalExpr> = Arc::new(Column::new("x", 0));
1208 let range_partitioning =
1209 fixture.range_partitioning([0, 1], vec![int_split_point([10, 100])]);
1210 let mapping = ProjectionMapping::from_iter([
1211 (
1212 fixture.col(0),
1213 ProjectionTargets::from(vec![(Arc::clone(&target), 0)]),
1214 ),
1215 (
1216 fixture.col(1),
1217 ProjectionTargets::from(vec![(Arc::clone(&target), 0)]),
1218 ),
1219 ]);
1220
1221 let projected = range_partitioning.project(&mapping, &fixture.eq_properties);
1222 let Partitioning::UnknownPartitioning(partition_count) = projected else {
1223 panic!("expected UnknownPartitioning, got {projected:?}");
1224 };
1225 assert_eq!(partition_count, 2);
1226
1227 Ok(())
1228 }
1229
1230 #[test]
1231 fn range_partitioning_key_distribution_satisfaction() -> Result<()> {
1232 let fixture = PartitioningTestFixture::int64(&["a", "b", "c"])?;
1233 let range_a = fixture.range_partitioning([0], vec![int_split_point([10])]);
1234 let range_ab =
1235 fixture.range_partitioning([0, 1], vec![int_split_point([10, 100])]);
1236
1237 assert_satisfaction(
1238 "exact single key",
1239 &range_a,
1240 &fixture.key_distribution([0]),
1241 &fixture.eq_properties,
1242 PartitioningSatisfaction::Exact,
1243 PartitioningSatisfaction::Exact,
1244 );
1245 assert_satisfaction(
1246 "exact compound key",
1247 &range_ab,
1248 &fixture.key_distribution([0, 1]),
1249 &fixture.eq_properties,
1250 PartitioningSatisfaction::Exact,
1251 PartitioningSatisfaction::Exact,
1252 );
1253 assert_satisfaction(
1254 "subset key",
1255 &range_a,
1256 &fixture.key_distribution([0, 1]),
1257 &fixture.eq_properties,
1258 PartitioningSatisfaction::Subset,
1259 PartitioningSatisfaction::NotSatisfied,
1260 );
1261 assert_satisfaction(
1262 "incompatible key",
1263 &range_a,
1264 &fixture.key_distribution([1]),
1265 &fixture.eq_properties,
1266 PartitioningSatisfaction::NotSatisfied,
1267 PartitioningSatisfaction::NotSatisfied,
1268 );
1269
1270 let mut eq_properties = fixture.eq_properties.clone();
1271 eq_properties.add_equal_conditions(fixture.col(0), fixture.col(2))?;
1272 assert_satisfaction(
1273 "equivalent subset key",
1274 &range_a,
1275 &fixture.key_distribution([1, 2]),
1276 &eq_properties,
1277 PartitioningSatisfaction::Subset,
1278 PartitioningSatisfaction::NotSatisfied,
1279 );
1280
1281 let mut eq_properties = fixture.eq_properties.clone();
1282 eq_properties.add_equal_conditions(fixture.col(0), fixture.col(1))?;
1283 assert_satisfaction(
1284 "equivalent exact key",
1285 &range_a,
1286 &fixture.key_distribution([1]),
1287 &eq_properties,
1288 PartitioningSatisfaction::Exact,
1289 PartitioningSatisfaction::Exact,
1290 );
1291
1292 Ok(())
1293 }
1294}
1295
1296#[cfg(all(test, feature = "proto"))]
1297mod ordering_proto_tests {
1298 use std::sync::Arc;
1299
1300 use arrow::compute::SortOptions;
1301 use arrow::datatypes::{DataType, Field, Schema};
1302 use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
1303 use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
1304 use datafusion_physical_expr_common::sort_expr::{
1305 LexRequirement, PhysicalSortExpr, PhysicalSortRequirement,
1306 sort_exprs_try_from_proto, sort_exprs_try_to_proto,
1307 };
1308
1309 use crate::expressions::Column;
1310 use crate::proto_test_util::{StubDecoder, StubEncoder};
1311
1312 fn schema() -> Schema {
1313 Schema::new(vec![Field::new("a", DataType::Int32, false)])
1314 }
1315
1316 fn sort_expr(descending: bool, nulls_first: bool) -> PhysicalSortExpr {
1317 PhysicalSortExpr::new(
1318 Arc::new(Column::new("a", 0)),
1319 SortOptions {
1320 descending,
1321 nulls_first,
1322 },
1323 )
1324 }
1325
1326 #[test]
1327 fn sort_exprs_round_trip_preserves_options_and_order() {
1328 let encoder = StubEncoder::ok();
1329 let encode_ctx = PhysicalExprEncodeCtx::new(&encoder);
1330 let exprs = vec![sort_expr(true, false), sort_expr(false, true)];
1331
1332 let nodes = sort_exprs_try_to_proto(&exprs, &encode_ctx).unwrap();
1333 assert_eq!(
1335 nodes
1336 .iter()
1337 .map(|node| (node.asc, node.nulls_first))
1338 .collect::<Vec<_>>(),
1339 vec![(false, false), (true, true)]
1340 );
1341
1342 let schema = schema();
1343 let decoder = StubDecoder::ok();
1344 let decode_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1345 let decoded = sort_exprs_try_from_proto(&nodes, &decode_ctx).unwrap();
1346 assert_eq!(
1347 decoded.iter().map(|expr| expr.options).collect::<Vec<_>>(),
1348 exprs.iter().map(|expr| expr.options).collect::<Vec<_>>()
1349 );
1350 }
1351
1352 #[test]
1353 fn sort_exprs_accepts_owned_requirements() {
1354 let encoder = StubEncoder::ok();
1355 let encode_ctx = PhysicalExprEncodeCtx::new(&encoder);
1356 let requirement = LexRequirement::from([PhysicalSortRequirement::new(
1357 Arc::new(Column::new("a", 0)),
1358 Some(SortOptions {
1359 descending: true,
1360 nulls_first: true,
1361 }),
1362 )]);
1363
1364 let nodes = sort_exprs_try_to_proto(
1365 requirement
1366 .iter()
1367 .map(|req| PhysicalSortExpr::from(req.clone())),
1368 &encode_ctx,
1369 )
1370 .unwrap();
1371
1372 assert_eq!(nodes.len(), 1);
1373 assert!(!nodes[0].asc);
1374 assert!(nodes[0].nulls_first);
1375 }
1376
1377 #[test]
1378 fn sort_exprs_propagate_encode_errors() {
1379 let encoder = StubEncoder::failing_on(2);
1380 let encode_ctx = PhysicalExprEncodeCtx::new(&encoder);
1381 let exprs = vec![sort_expr(false, false), sort_expr(true, true)];
1382
1383 let err = sort_exprs_try_to_proto(&exprs, &encode_ctx).unwrap_err();
1384 assert!(err.to_string().contains("stub encode failure on call 2"));
1385 }
1386
1387 #[test]
1388 fn sort_exprs_reject_missing_inner_expr() {
1389 let encoder = StubEncoder::ok();
1390 let encode_ctx = PhysicalExprEncodeCtx::new(&encoder);
1391 let mut nodes =
1392 sort_exprs_try_to_proto(&[sort_expr(false, false)], &encode_ctx).unwrap();
1393 nodes[0].expr = None;
1394
1395 let schema = schema();
1396 let decoder = StubDecoder::ok();
1397 let decode_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1398 let err = sort_exprs_try_from_proto(&nodes, &decode_ctx).unwrap_err();
1399 assert!(
1400 err.to_string()
1401 .contains("PhysicalSortExpr is missing required field 'expr'")
1402 );
1403 }
1404}
1405
1406#[cfg(all(test, feature = "proto"))]
1410mod partition_count_proto_tests {
1411 use std::sync::Arc;
1412
1413 use arrow::datatypes::{DataType, Field, Schema};
1414 use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
1415 use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
1416 use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
1417 use datafusion_proto_models::protobuf;
1418
1419 use super::{Partitioning, partition_count, wire_partition_count};
1420 use crate::expressions::Column;
1421 use crate::proto_test_util::{StubDecoder, StubEncoder, column_node};
1422
1423 fn partitioning_node(
1424 method: protobuf::partitioning::PartitionMethod,
1425 ) -> protobuf::Partitioning {
1426 protobuf::Partitioning {
1427 partition_method: Some(method),
1428 }
1429 }
1430
1431 fn counted_methods(count: u64) -> Vec<protobuf::partitioning::PartitionMethod> {
1435 use protobuf::partitioning::PartitionMethod;
1436
1437 vec![
1438 PartitionMethod::RoundRobin(count),
1439 PartitionMethod::Unknown(count),
1440 PartitionMethod::Hash(protobuf::PhysicalHashRepartition {
1441 hash_expr: vec![column_node("a")],
1442 partition_count: count,
1443 }),
1444 ]
1445 }
1446
1447 #[test]
1448 fn partition_count_round_trips_at_the_usize_ceiling() {
1449 let wire = wire_partition_count(usize::MAX).unwrap();
1452 assert_eq!(wire, u64::try_from(usize::MAX).unwrap());
1453 assert_eq!(partition_count(wire).unwrap(), usize::MAX);
1454 }
1455
1456 #[test]
1457 fn out_of_range_partition_count_is_reported_not_wrapped() {
1458 let narrowed = partition_count(u64::MAX);
1464
1465 #[cfg(target_pointer_width = "64")]
1466 assert_eq!(narrowed.unwrap(), usize::MAX);
1467
1468 #[cfg(not(target_pointer_width = "64"))]
1469 assert!(
1470 narrowed
1471 .unwrap_err()
1472 .to_string()
1473 .contains("Partition count 18446744073709551615 exceeds usize::MAX")
1474 );
1475 }
1476
1477 #[test]
1478 fn try_from_proto_narrows_every_counted_variant() {
1479 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
1480 let decoder = StubDecoder::ok();
1481 let decode_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1482
1483 for method in counted_methods(u64::MAX) {
1484 let decoded =
1485 Partitioning::try_from_proto(&partitioning_node(method), &decode_ctx);
1486
1487 #[cfg(target_pointer_width = "64")]
1488 assert_eq!(decoded.unwrap().unwrap().partition_count(), usize::MAX);
1489
1490 #[cfg(not(target_pointer_width = "64"))]
1491 assert!(
1492 decoded
1493 .unwrap_err()
1494 .to_string()
1495 .contains("exceeds usize::MAX")
1496 );
1497 }
1498 }
1499
1500 #[test]
1501 fn try_to_proto_widens_every_counted_variant() {
1502 use protobuf::partitioning::PartitionMethod;
1503
1504 let encoder = StubEncoder::ok();
1505 let encode_ctx = PhysicalExprEncodeCtx::new(&encoder);
1506 let hash_key: Arc<dyn PhysicalExpr> = Arc::new(Column::new("a", 0));
1507
1508 let encoded = [
1509 Partitioning::RoundRobinBatch(usize::MAX),
1510 Partitioning::UnknownPartitioning(usize::MAX),
1511 Partitioning::Hash(vec![hash_key], usize::MAX),
1512 ]
1513 .iter()
1514 .map(|partitioning| {
1515 match partitioning
1516 .try_to_proto(&encode_ctx)
1517 .unwrap()
1518 .partition_method
1519 {
1520 Some(PartitionMethod::RoundRobin(n) | PartitionMethod::Unknown(n)) => n,
1521 Some(PartitionMethod::Hash(hash)) => hash.partition_count,
1522 other => panic!("expected a counted partition method, got {other:?}"),
1523 }
1524 })
1525 .collect::<Vec<_>>();
1526
1527 assert_eq!(encoded, vec![u64::try_from(usize::MAX).unwrap(); 3]);
1529 }
1530}