datafusion_physical_plan/
distribution_requirements.rs1use datafusion_common::{Result, internal_err};
21use datafusion_physical_expr::{Distribution, Partitioning, PartitioningSatisfaction};
22
23use crate::execution_plan::{ExecutionPlan, ExecutionPlanProperties, InvariantLevel};
24
25#[non_exhaustive]
58#[derive(Debug, Clone)]
59pub struct InputDistributionRequirements {
60 children: Vec<ChildDistributionRequirement>,
62 co_partitioned: Option<Vec<usize>>,
64}
65
66#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
68pub struct ChildSatisfactionOptions {
69 allow_subset: bool,
70}
71
72impl ChildSatisfactionOptions {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn with_allow_subset(mut self, allow_subset: bool) -> Self {
81 self.allow_subset = allow_subset;
82 self
83 }
84
85 pub fn allow_subset(&self) -> bool {
87 self.allow_subset
88 }
89}
90
91impl InputDistributionRequirements {
92 pub fn new(per_child: Vec<Distribution>) -> Self {
94 let children = per_child
95 .into_iter()
96 .map(|distribution| ChildDistributionRequirement { distribution })
97 .collect();
98
99 Self {
100 children,
101 co_partitioned: None,
102 }
103 }
104
105 pub fn co_partitioned(per_child: Vec<Distribution>) -> Self {
122 debug_assert!(
123 per_child.len() >= 2,
124 "co-partitioned distribution requirements need at least two children"
125 );
126 let co_partitioned = (0..per_child.len()).collect();
127 let mut result = Self::new(per_child);
128 result.co_partitioned = Some(co_partitioned);
129 result
130 }
131
132 pub fn per_child_distributions(
134 &self,
135 ) -> impl ExactSizeIterator<Item = &Distribution> + '_ {
136 self.children.iter().map(|child| &child.distribution)
137 }
138
139 pub fn child_distribution(&self, child_idx: usize) -> Option<&Distribution> {
141 self.children
142 .get(child_idx)
143 .map(|child| &child.distribution)
144 }
145
146 pub fn into_per_child(self) -> Vec<Distribution> {
150 self.children
151 .into_iter()
152 .map(|child| child.distribution)
153 .collect()
154 }
155
156 pub fn child_satisfaction(
160 &self,
161 child_idx: usize,
162 child: &dyn ExecutionPlan,
163 options: ChildSatisfactionOptions,
164 ) -> Result<PartitioningSatisfaction> {
165 let Some(requirement) = self.children.get(child_idx) else {
166 return internal_err!(
167 "missing distribution requirement for child {child_idx}"
168 );
169 };
170
171 Ok(child.output_partitioning().satisfaction(
172 &requirement.distribution,
173 child.equivalence_properties(),
174 options.allow_subset(),
175 ))
176 }
177
178 #[doc(hidden)]
185 pub fn unsatisfied_co_partitioned_children(
186 &self,
187 plan_name: &str,
188 children: &[&dyn ExecutionPlan],
189 ) -> Result<Vec<usize>> {
190 self.validate_shape(plan_name, children.len())?;
191
192 let Some(co_partitioned) = &self.co_partitioned else {
193 return Ok(vec![]);
194 };
195 if self.co_partitioning_satisfied(co_partitioned, children) {
196 return Ok(vec![]);
197 }
198
199 Ok(co_partitioned.clone())
200 }
201
202 pub(crate) fn check_invariants<P: ExecutionPlan + ?Sized>(
204 &self,
205 plan: &P,
206 check: InvariantLevel,
207 ) -> Result<()> {
208 let children = plan.children();
209 self.validate_shape(plan.name(), children.len())?;
210
211 let children = children
212 .into_iter()
213 .map(|child| child.as_ref())
214 .collect::<Vec<_>>();
215 if matches!(check, InvariantLevel::Executable)
216 && let Some(co_partitioned) = &self.co_partitioned
217 && !self.co_partitioning_satisfied(co_partitioned, &children)
218 {
219 return internal_err!(
220 "{} requires children {:?} to be co-partitioned",
221 plan.name(),
222 co_partitioned
223 );
224 }
225
226 Ok(())
227 }
228
229 fn validate_shape(&self, plan_name: &str, children_len: usize) -> Result<()> {
230 if self.children.len() != children_len {
231 return internal_err!(
232 "{plan_name}::input_distribution_requirements returned incorrect child count: {} != {}",
233 self.children.len(),
234 children_len
235 );
236 }
237
238 if let Some(co_partitioned) = &self.co_partitioned {
239 if co_partitioned.len() < 2 {
240 return internal_err!(
241 "{plan_name} has invalid co-partitioning requirement: at least two children are required"
242 );
243 }
244 let mut seen = vec![false; self.children.len()];
245 for &child in co_partitioned {
246 validate_child_index(plan_name, child, self.children.len(), &mut seen)?;
247 if matches!(
248 self.children[child].distribution,
249 Distribution::UnspecifiedDistribution
250 ) {
251 return internal_err!(
252 "{plan_name} has invalid co-partitioning requirement: child {child} has unspecified distribution"
253 );
254 }
255 }
256 }
257
258 Ok(())
259 }
260
261 fn co_partitioning_satisfied(
262 &self,
263 co_partitioned: &[usize],
264 children: &[&dyn ExecutionPlan],
265 ) -> bool {
266 let first_idx = co_partitioned[0];
267 let first_requirement = &self.children[first_idx];
268 let first = children[first_idx];
269 let first_partitioning = first.output_partitioning();
270
271 if !first_partitioning
272 .satisfaction(
273 &first_requirement.distribution,
274 first.equivalence_properties(),
275 false,
276 )
277 .is_satisfied()
278 {
279 return false;
280 }
281
282 for &child_idx in co_partitioned.iter().skip(1) {
283 let requirement = &self.children[child_idx];
284 let child = children[child_idx];
285 if !child
286 .output_partitioning()
287 .satisfaction(
288 &requirement.distribution,
289 child.equivalence_properties(),
290 false,
291 )
292 .is_satisfied()
293 || !compatible_co_partitioning_layout(
294 first_partitioning,
295 child.output_partitioning(),
296 )
297 {
298 return false;
299 }
300 }
301
302 true
303 }
304}
305
306#[derive(Debug, Clone)]
308struct ChildDistributionRequirement {
309 distribution: Distribution,
310}
311
312fn validate_child_index(
313 plan_name: &str,
314 child_idx: usize,
315 child_count: usize,
316 seen: &mut [bool],
317) -> Result<()> {
318 if child_idx >= child_count {
319 return internal_err!(
320 "{plan_name} has invalid distribution requirement: child index {child_idx} out of bounds"
321 );
322 }
323 if seen[child_idx] {
324 return internal_err!(
325 "{plan_name} has invalid distribution requirement: child {child_idx} appears more than once"
326 );
327 }
328 seen[child_idx] = true;
329 Ok(())
330}
331
332fn compatible_co_partitioning_layout(
333 first_partitioning: &Partitioning,
334 other_partitioning: &Partitioning,
335) -> bool {
336 if first_partitioning.partition_count() == 1
337 && other_partitioning.partition_count() == 1
338 {
339 return true;
340 }
341
342 if first_partitioning.partition_count() != other_partitioning.partition_count() {
343 return false;
344 }
345
346 match (first_partitioning, other_partitioning) {
347 (Partitioning::Hash(_, _), Partitioning::Hash(_, _)) => true,
348 (Partitioning::Range(left), Partitioning::Range(right)) => {
349 left.split_points() == right.split_points()
350 && left.ordering().len() == right.ordering().len()
351 && left
352 .ordering()
353 .iter()
354 .zip(right.ordering())
355 .all(|(left, right)| left.options == right.options)
356 }
357 _ => false,
358 }
359}