Skip to content

Commit bd4532a

Browse files
committed
Address Rich-T-kid review on #23128: assert! + comment tightening
* Dispatcher (`mod.rs`): change the `list_size < 0` guard from a `return not_impl_err!` to a bare `assert!`. `group_column_supported_type` already rejects negative sizes at the schema-check layer, so any reachable path with `list_size < 0` is a programmer bug rather than an unsupported case worth returning gracefully. Split the existing test into an allow-list case (checked via `bool`) and a dispatcher case (verified with `#[should_panic]`) so both surfaces stay covered. * Constructor (`fixed_size_list.rs`): fold the multi-line invariant comment above `assert!` into the assert message itself, which now reads "requires non-negative list size (allow-list / dispatcher should have rejected earlier)". Simpler to read at the panic site, and the rationale surfaces exactly where a future reader would want it (the panic backtrace). Same trim on `list_len_usize`. * Dispatcher consistency-fuzz corpus (`mod.rs`): shorten the FixedSizeList<non-primitive> comment to a one-liner pointing at EPIC #22715, instead of the two-line prose explanation. Tests: `cargo test -p datafusion-physical-plan --lib multi_group_by` now runs 42/42 (up from 41), including the split negative-size cases and the new `#[should_panic]` guarantee. Clippy clean.
1 parent 56ecc8d commit bd4532a

2 files changed

Lines changed: 33 additions & 41 deletions

File tree

datafusion/physical-plan/src/aggregates/group_values/multi_group_by/fixed_size_list.rs

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,9 @@ where
6060
"FixedSizeListGroupValueBuilder built with non-FixedSizeList type {other:?}"
6161
),
6262
};
63-
// `group_column_supported_type` and `make_group_column` both reject
64-
// negative `list_len` upstream so this branch should be unreachable
65-
// for any schema that survives the allow-list. Assert defensively
66-
// to fail fast (and with a clear message) if a direct caller ever
67-
// bypasses the factory with an invalid Arrow type.
6863
assert!(
6964
list_len >= 0,
70-
"FixedSizeListGroupValueBuilder requires non-negative list size, got {list_len}"
65+
"FixedSizeListGroupValueBuilder requires non-negative list size (allow-list / dispatcher should have rejected earlier), got {list_len}"
7166
);
7267
let child = PrimitiveGroupValueBuilder::<T, true>::new(field.data_type().clone());
7368
Self {
@@ -79,12 +74,9 @@ where
7974
}
8075
}
8176

82-
/// Lossless widening to `usize`. The constructor asserts
83-
/// `list_len >= 0`, so the conversion is well-defined. We still go
84-
/// through `try_from` rather than `as usize` so any future
85-
/// invariant break surfaces as a panic with a clear message rather
86-
/// than a silent wrap.
8777
fn list_len_usize(&self) -> usize {
78+
// `try_from` (vs `as usize`) so any future invariant break
79+
// surfaces as a panic instead of a silent wrap.
8880
usize::try_from(self.list_len)
8981
.expect("list_len validated >= 0 in `new`; conversion to usize cannot fail")
9082
}
@@ -439,14 +431,11 @@ mod tests {
439431

440432
/// `FixedSizeList` carries its size as `i32`; the enum lets callers
441433
/// construct a negative size programmatically even though Arrow
442-
/// considers it invalid. Both the supported-type allow-list and the
443-
/// dispatcher must reject such a type so the cast inside the
444-
/// builder cannot wrap to a huge `usize` and trigger panics / OOM.
434+
/// considers it invalid. The supported-type allow-list must reject
435+
/// such a type so it never reaches the builder path.
445436
#[test]
446-
fn negative_list_size_is_rejected_by_allow_list_and_dispatcher() {
447-
use crate::aggregates::group_values::multi_group_by::{
448-
group_column_supported_type, make_group_column,
449-
};
437+
fn negative_list_size_rejected_by_allow_list() {
438+
use crate::aggregates::group_values::multi_group_by::group_column_supported_type;
450439
use arrow::datatypes::Field;
451440

452441
let invalid = DataType::FixedSizeList(
@@ -458,18 +447,23 @@ mod tests {
458447
!group_column_supported_type(&invalid),
459448
"allow-list must reject FixedSizeList(_, -1)"
460449
);
450+
}
461451

462-
let field = Field::new("col", invalid, true);
463-
let result = make_group_column(&field);
464-
let err = match result {
465-
Ok(_) => panic!("dispatcher must reject FixedSizeList with negative size"),
466-
Err(e) => e,
467-
};
468-
let msg = err.to_string();
469-
assert!(
470-
msg.contains("negative size") && msg.contains("-1"),
471-
"error should mention the invalid negative size, got: {msg}"
452+
/// The dispatcher is a belt-and-suspenders guard for direct callers
453+
/// that bypass the allow-list. A negative `list_size` must panic
454+
/// there rather than wrap in the builder.
455+
#[test]
456+
#[should_panic(expected = "FixedSizeList requires non-negative size")]
457+
fn negative_list_size_panics_in_dispatcher() {
458+
use crate::aggregates::group_values::multi_group_by::make_group_column;
459+
use arrow::datatypes::Field;
460+
461+
let invalid = DataType::FixedSizeList(
462+
Arc::new(Field::new("item", DataType::Int32, true)),
463+
-1,
472464
);
465+
let field = Field::new("col", invalid, true);
466+
let _ = make_group_column(&field);
473467
}
474468

475469
#[test]

datafusion/physical-plan/src/aggregates/group_values/multi_group_by/mod.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,16 +1096,15 @@ fn make_group_column(field: &Field) -> Result<Box<dyn GroupColumn>> {
10961096
}
10971097
}
10981098
DataType::FixedSizeList(ref child_field, list_size) => {
1099-
// Defense in depth against an invalid Arrow type that the
1100-
// allow-list might have missed: negative `list_size` would
1101-
// wrap when cast to `usize` and trigger panics / OOM in the
1102-
// builder. Reject explicitly here as well so direct callers
1103-
// of `make_group_column` fail safely.
1104-
if list_size < 0 {
1105-
return not_impl_err!(
1106-
"FixedSizeList with negative size {list_size} not supported in GroupValuesColumn"
1107-
);
1108-
}
1099+
// `group_column_supported_type` already rejects negative
1100+
// `list_size`; this assert is a defensive belt-and-suspenders
1101+
// check so a direct caller of `make_group_column` that bypasses
1102+
// the allow-list can't wrap the `i32 as usize` cast in the
1103+
// builder into a huge value.
1104+
assert!(
1105+
list_size >= 0,
1106+
"FixedSizeList requires non-negative size, got {list_size}"
1107+
);
11091108
// `group_column_supported_type` already restricts the child to
11101109
// the primitive subset supported here. Any unsupported child
11111110
// type returned `false` upstream and was routed to the
@@ -1430,9 +1429,8 @@ mod tests {
14301429
DataType::Time64(arrow::datatypes::TimeUnit::Millisecond),
14311430
DataType::Time32(arrow::datatypes::TimeUnit::Microsecond),
14321431
DataType::Time32(arrow::datatypes::TimeUnit::Nanosecond),
1433-
// FixedSizeList<non-primitive>: only primitive children are
1434-
// covered by this PR; nested children depend on the
1435-
// List / Struct builders that follow in the EPIC sequence.
1432+
// FixedSizeList<non-primitive>: primitive-only in this PR;
1433+
// nested children land later in EPIC #22715.
14361434
DataType::FixedSizeList(
14371435
Arc::new(Field::new("item", DataType::Utf8, true)),
14381436
2,

0 commit comments

Comments
 (0)