Skip to content

Commit e63ad80

Browse files
asolimandoclaude
andcommitted
fix: report LowerEqual cardinality effect for CoalescePartitionsExec with fetch
`CoalescePartitionsExec::cardinality_effect` returned `Equal` even when a fetch is set, so `PassthroughStatisticsProvider` matched it and copied the child statistics, discarding the fetch that the operator's own `statistics_from_inputs` applies. Return `LowerEqual` when a fetch is set, as `SortExec` and `SortPreservingMergeExec` already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 982fca6 commit e63ad80

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

‎datafusion/physical-plan/src/coalesce_partitions.rs‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,11 @@ impl ExecutionPlan for CoalescePartitionsExec {
304304
}
305305

306306
fn cardinality_effect(&self) -> CardinalityEffect {
307-
CardinalityEffect::Equal
307+
if self.fetch.is_none() {
308+
CardinalityEffect::Equal
309+
} else {
310+
CardinalityEffect::LowerEqual
311+
}
308312
}
309313

310314
/// Tries to swap `projection` with its input, which is known to be a
@@ -698,4 +702,21 @@ mod tests {
698702

699703
Ok(())
700704
}
705+
706+
#[test]
707+
fn test_cardinality_effect_with_fetch() {
708+
let input = test::scan_partitioned(4);
709+
710+
let coalesce = CoalescePartitionsExec::new(Arc::clone(&input));
711+
assert!(matches!(
712+
coalesce.cardinality_effect(),
713+
CardinalityEffect::Equal
714+
));
715+
716+
let coalesce = CoalescePartitionsExec::new(input).with_fetch(Some(10));
717+
assert!(matches!(
718+
coalesce.cardinality_effect(),
719+
CardinalityEffect::LowerEqual
720+
));
721+
}
701722
}

‎datafusion/physical-plan/src/operator_statistics/mod.rs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,22 @@ mod tests {
19131913
Ok(())
19141914
}
19151915

1916+
#[test]
1917+
fn test_passthrough_skips_coalesce_partitions_with_fetch() -> Result<()> {
1918+
use crate::coalesce_partitions::CoalescePartitionsExec;
1919+
1920+
let registry = StatisticsRegistry::with_providers(vec![Arc::new(
1921+
PassthroughStatisticsProvider,
1922+
)]);
1923+
let source = make_source(1000);
1924+
let coalesce: Arc<dyn ExecutionPlan> =
1925+
Arc::new(CoalescePartitionsExec::new(source).with_fetch(Some(10)));
1926+
1927+
let stats = compute(&registry, coalesce.as_ref())?;
1928+
assert_eq!(stats.base.num_rows, Precision::Exact(10));
1929+
Ok(())
1930+
}
1931+
19161932
#[test]
19171933
fn test_chain_priority() -> Result<()> {
19181934
let mut registry = StatisticsRegistry::new();

0 commit comments

Comments
 (0)