1mod bool;
5mod decimal;
6mod extension;
7mod primitive;
8mod varbin;
9
10use std::sync::LazyLock;
11
12use vortex_error::VortexExpect;
13use vortex_error::VortexResult;
14use vortex_error::vortex_bail;
15
16use self::bool::accumulate_bool;
17use self::decimal::accumulate_decimal;
18use self::extension::accumulate_extension;
19use self::primitive::accumulate_primitive;
20use self::varbin::accumulate_varbinview;
21use crate::ArrayRef;
22use crate::Canonical;
23use crate::Columnar;
24use crate::ExecutionCtx;
25use crate::aggregate_fn::Accumulator;
26use crate::aggregate_fn::AggregateFnId;
27use crate::aggregate_fn::AggregateFnVTable;
28use crate::aggregate_fn::DynAccumulator;
29use crate::aggregate_fn::EmptyOptions;
30use crate::dtype::DType;
31use crate::dtype::FieldNames;
32use crate::dtype::Nullability;
33use crate::dtype::StructFields;
34use crate::expr::stats::Precision;
35use crate::expr::stats::Stat;
36use crate::expr::stats::StatsProvider;
37use crate::partial_ord::partial_max;
38use crate::partial_ord::partial_min;
39use crate::scalar::Scalar;
40
41static NAMES: LazyLock<FieldNames> = LazyLock::new(|| FieldNames::from(["min", "max"]));
42
43pub fn min_max(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Option<MinMaxResult>> {
48 let cached_min = array.statistics().get(Stat::Min).as_exact();
50 let cached_max = array.statistics().get(Stat::Max).as_exact();
51 if let Some((min, max)) = cached_min.zip(cached_max) {
52 let non_nullable_dtype = array.dtype().as_nonnullable();
53 return Ok(Some(MinMaxResult {
54 min: min.cast(&non_nullable_dtype)?,
55 max: max.cast(&non_nullable_dtype)?,
56 }));
57 }
58
59 if array.is_empty() || array.valid_count(ctx)? == 0 {
61 return Ok(None);
62 }
63
64 if !minmax_compute_supported_dtype(array.dtype()) {
66 return Ok(None);
67 }
68
69 let mut acc = Accumulator::try_new(MinMax, EmptyOptions, array.dtype().clone())?;
71 acc.accumulate(array, ctx)?;
72 let result_scalar = acc.finish()?;
73 let result = MinMaxResult::from_scalar(result_scalar)?;
74
75 if let Some(r) = &result {
77 if let Some(min_value) = r.min.value() {
78 array
79 .statistics()
80 .set(Stat::Min, Precision::Exact(min_value.clone()));
81 }
82 if let Some(max_value) = r.max.value() {
83 array
84 .statistics()
85 .set(Stat::Max, Precision::Exact(max_value.clone()));
86 }
87 }
88
89 Ok(result)
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct MinMaxResult {
95 pub min: Scalar,
96 pub max: Scalar,
97}
98
99impl MinMaxResult {
100 pub fn from_scalar(scalar: Scalar) -> VortexResult<Option<Self>> {
102 if scalar.is_null() {
103 Ok(None)
104 } else {
105 let min = scalar
106 .as_struct()
107 .field_by_idx(0)
108 .vortex_expect("missing min field");
109 let max = scalar
110 .as_struct()
111 .field_by_idx(1)
112 .vortex_expect("missing max field");
113 Ok(Some(MinMaxResult { min, max }))
114 }
115 }
116}
117
118#[derive(Clone, Debug)]
123pub struct MinMax;
124
125pub struct MinMaxPartial {
127 min: Option<Scalar>,
128 max: Option<Scalar>,
129 element_dtype: DType,
130}
131
132impl MinMaxPartial {
133 fn merge(&mut self, local: Option<MinMaxResult>) {
135 let Some(MinMaxResult { min, max }) = local else {
136 return;
137 };
138
139 self.min = Some(match self.min.take() {
140 Some(current) => partial_min(min, current).vortex_expect("incomparable min scalars"),
141 None => min,
142 });
143
144 self.max = Some(match self.max.take() {
145 Some(current) => partial_max(max, current).vortex_expect("incomparable max scalars"),
146 None => max,
147 });
148 }
149}
150
151pub fn make_minmax_dtype(element_dtype: &DType) -> DType {
153 DType::Struct(
154 StructFields::new(
155 NAMES.clone(),
156 vec![
157 element_dtype.as_nonnullable(),
158 element_dtype.as_nonnullable(),
159 ],
160 ),
161 Nullability::Nullable,
162 )
163}
164
165fn minmax_supported_dtype(input_dtype: &DType) -> bool {
166 match input_dtype {
167 DType::Bool(_)
168 | DType::Primitive(..)
169 | DType::Decimal(..)
170 | DType::Utf8(..)
171 | DType::Binary(..)
172 | DType::Extension(..) => true,
173 DType::List(element_dtype, _) => minmax_supported_dtype(element_dtype),
174 DType::FixedSizeList(element_dtype, ..) => minmax_supported_dtype(element_dtype),
175 _ => false,
176 }
177}
178
179fn minmax_compute_supported_dtype(input_dtype: &DType) -> bool {
185 matches!(
186 input_dtype,
187 DType::Bool(_)
188 | DType::Primitive(..)
189 | DType::Decimal(..)
190 | DType::Utf8(..)
191 | DType::Binary(..)
192 | DType::Extension(..)
193 )
194}
195
196impl AggregateFnVTable for MinMax {
197 type Options = EmptyOptions;
198 type Partial = MinMaxPartial;
199
200 fn id(&self) -> AggregateFnId {
201 AggregateFnId::new("vortex.min_max")
202 }
203
204 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
205 Ok(None)
206 }
207
208 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
209 minmax_supported_dtype(input_dtype).then(|| make_minmax_dtype(input_dtype))
210 }
211
212 fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option<DType> {
213 self.return_dtype(options, input_dtype)
214 }
215
216 fn empty_partial(
217 &self,
218 _options: &Self::Options,
219 input_dtype: &DType,
220 ) -> VortexResult<Self::Partial> {
221 Ok(MinMaxPartial {
222 min: None,
223 max: None,
224 element_dtype: input_dtype.clone(),
225 })
226 }
227
228 fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> {
229 let local = MinMaxResult::from_scalar(other)?;
230 partial.merge(local);
231 Ok(())
232 }
233
234 fn to_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
235 let dtype = make_minmax_dtype(&partial.element_dtype);
236 Ok(match (&partial.min, &partial.max) {
237 (Some(min), Some(max)) => Scalar::struct_(dtype, vec![min.clone(), max.clone()]),
238 _ => Scalar::null(dtype),
239 })
240 }
241
242 fn reset(&self, partial: &mut Self::Partial) {
243 partial.min = None;
244 partial.max = None;
245 }
246
247 #[inline]
248 fn is_saturated(&self, _partial: &Self::Partial) -> bool {
249 false
250 }
251
252 fn accumulate(
253 &self,
254 partial: &mut Self::Partial,
255 batch: &Columnar,
256 ctx: &mut ExecutionCtx,
257 ) -> VortexResult<()> {
258 match batch {
259 Columnar::Constant(c) => {
260 let scalar = c.scalar();
261 if scalar.is_null() {
262 return Ok(());
263 }
264 if scalar.as_primitive_opt().is_some_and(|p| p.is_nan()) {
266 return Ok(());
267 }
268 let non_nullable_dtype = scalar.dtype().as_nonnullable();
269 let cast = scalar.cast(&non_nullable_dtype)?;
270 partial.merge(Some(MinMaxResult {
271 min: cast.clone(),
272 max: cast,
273 }));
274 Ok(())
275 }
276 Columnar::Canonical(c) => match c {
277 Canonical::Primitive(p) => accumulate_primitive(partial, p, ctx),
278 Canonical::Bool(b) => accumulate_bool(partial, b, ctx),
279 Canonical::VarBinView(v) => accumulate_varbinview(partial, v),
280 Canonical::Decimal(d) => accumulate_decimal(partial, d, ctx),
281 Canonical::Extension(e) => accumulate_extension(partial, e, ctx),
282 Canonical::Null(_) => Ok(()),
283 Canonical::Struct(_)
284 | Canonical::List(_)
285 | Canonical::FixedSizeList(_)
286 | Canonical::Variant(_) => {
287 vortex_bail!("Unsupported canonical type for min_max: {}", batch.dtype())
288 }
289 },
290 }
291 }
292
293 fn finalize(&self, partials: ArrayRef) -> VortexResult<ArrayRef> {
294 Ok(partials)
295 }
296
297 fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
298 self.to_scalar(partial)
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use std::sync::Arc;
305
306 use vortex_buffer::BitBuffer;
307 use vortex_buffer::buffer;
308 use vortex_error::VortexExpect;
309 use vortex_error::VortexResult;
310
311 use crate::IntoArray as _;
312 use crate::LEGACY_SESSION;
313 use crate::VortexSessionExecute;
314 use crate::aggregate_fn::Accumulator;
315 use crate::aggregate_fn::AggregateFnVTable;
316 use crate::aggregate_fn::DynAccumulator;
317 use crate::aggregate_fn::EmptyOptions;
318 use crate::aggregate_fn::fns::min_max::MinMax;
319 use crate::aggregate_fn::fns::min_max::MinMaxResult;
320 use crate::aggregate_fn::fns::min_max::make_minmax_dtype;
321 use crate::aggregate_fn::fns::min_max::min_max;
322 use crate::arrays::BoolArray;
323 use crate::arrays::ChunkedArray;
324 use crate::arrays::ConstantArray;
325 use crate::arrays::DecimalArray;
326 use crate::arrays::FixedSizeListArray;
327 use crate::arrays::ListArray;
328 use crate::arrays::NullArray;
329 use crate::arrays::PrimitiveArray;
330 use crate::arrays::VarBinArray;
331 use crate::dtype::DType;
332 use crate::dtype::DecimalDType;
333 use crate::dtype::Nullability;
334 use crate::dtype::PType;
335 use crate::scalar::DecimalValue;
336 use crate::scalar::Scalar;
337 use crate::scalar::ScalarValue;
338 use crate::validity::Validity;
339
340 #[test]
341 fn test_prim_min_max() -> VortexResult<()> {
342 let p = PrimitiveArray::new(buffer![1, 2, 3], Validity::NonNullable).into_array();
343 let mut ctx = LEGACY_SESSION.create_execution_ctx();
344 assert_eq!(
345 min_max(&p, &mut ctx)?,
346 Some(MinMaxResult {
347 min: 1.into(),
348 max: 3.into()
349 })
350 );
351 Ok(())
352 }
353
354 #[test]
355 fn test_prim_min_max_multiple_null_runs() -> VortexResult<()> {
356 let p = PrimitiveArray::from_option_iter([
359 Some(5i32),
360 Some(3),
361 None,
362 None,
363 Some(9),
364 None,
365 Some(1),
366 Some(7),
367 ])
368 .into_array();
369 let mut ctx = LEGACY_SESSION.create_execution_ctx();
370 assert_eq!(
371 min_max(&p, &mut ctx)?,
372 Some(MinMaxResult {
373 min: 1.into(),
374 max: 9.into()
375 })
376 );
377 Ok(())
378 }
379
380 #[test]
381 fn test_bool_min_max() -> VortexResult<()> {
382 let mut ctx = LEGACY_SESSION.create_execution_ctx();
383
384 let all_true = BoolArray::new(
385 BitBuffer::from([true, true, true].as_slice()),
386 Validity::NonNullable,
387 )
388 .into_array();
389 assert_eq!(
390 min_max(&all_true, &mut ctx)?,
391 Some(MinMaxResult {
392 min: true.into(),
393 max: true.into()
394 })
395 );
396
397 let all_false = BoolArray::new(
398 BitBuffer::from([false, false, false].as_slice()),
399 Validity::NonNullable,
400 )
401 .into_array();
402 assert_eq!(
403 min_max(&all_false, &mut ctx)?,
404 Some(MinMaxResult {
405 min: false.into(),
406 max: false.into()
407 })
408 );
409
410 let mixed = BoolArray::new(
411 BitBuffer::from([false, true, false].as_slice()),
412 Validity::NonNullable,
413 )
414 .into_array();
415 assert_eq!(
416 min_max(&mixed, &mut ctx)?,
417 Some(MinMaxResult {
418 min: false.into(),
419 max: true.into()
420 })
421 );
422 Ok(())
423 }
424
425 #[test]
426 fn test_null_array() -> VortexResult<()> {
427 let p = NullArray::new(1).into_array();
428 let mut ctx = LEGACY_SESSION.create_execution_ctx();
429 assert_eq!(min_max(&p, &mut ctx)?, None);
430 Ok(())
431 }
432
433 #[test]
434 fn test_prim_nan() -> VortexResult<()> {
435 let array = PrimitiveArray::new(
436 buffer![f32::NAN, -f32::NAN, -1.0, 1.0],
437 Validity::NonNullable,
438 );
439 let mut ctx = LEGACY_SESSION.create_execution_ctx();
440 let result = min_max(&array.into_array(), &mut ctx)?.vortex_expect("should have result");
441 assert_eq!(f32::try_from(&result.min)?, -1.0);
442 assert_eq!(f32::try_from(&result.max)?, 1.0);
443 Ok(())
444 }
445
446 #[test]
447 fn test_prim_inf() -> VortexResult<()> {
448 let array = PrimitiveArray::new(
449 buffer![f32::INFINITY, f32::NEG_INFINITY, -1.0, 1.0],
450 Validity::NonNullable,
451 );
452 let mut ctx = LEGACY_SESSION.create_execution_ctx();
453 let result = min_max(&array.into_array(), &mut ctx)?.vortex_expect("should have result");
454 assert_eq!(f32::try_from(&result.min)?, f32::NEG_INFINITY);
455 assert_eq!(f32::try_from(&result.max)?, f32::INFINITY);
456 Ok(())
457 }
458
459 #[test]
460 fn test_multi_batch() -> VortexResult<()> {
461 let mut ctx = LEGACY_SESSION.create_execution_ctx();
462 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
463 let mut acc = Accumulator::try_new(MinMax, EmptyOptions, dtype)?;
464
465 let batch1 = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array();
466 acc.accumulate(&batch1, &mut ctx)?;
467
468 let batch2 = PrimitiveArray::new(buffer![3i32, 25], Validity::NonNullable).into_array();
469 acc.accumulate(&batch2, &mut ctx)?;
470
471 let result = MinMaxResult::from_scalar(acc.finish()?)?.vortex_expect("should have result");
472 assert_eq!(result.min, Scalar::from(3i32));
473 assert_eq!(result.max, Scalar::from(25i32));
474 Ok(())
475 }
476
477 #[test]
478 fn test_finish_resets_state() -> VortexResult<()> {
479 let mut ctx = LEGACY_SESSION.create_execution_ctx();
480 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
481 let mut acc = Accumulator::try_new(MinMax, EmptyOptions, dtype)?;
482
483 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
484 acc.accumulate(&batch1, &mut ctx)?;
485 let result1 = MinMaxResult::from_scalar(acc.finish()?)?.vortex_expect("should have result");
486 assert_eq!(result1.min, Scalar::from(10i32));
487 assert_eq!(result1.max, Scalar::from(20i32));
488
489 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
490 acc.accumulate(&batch2, &mut ctx)?;
491 let result2 = MinMaxResult::from_scalar(acc.finish()?)?.vortex_expect("should have result");
492 assert_eq!(result2.min, Scalar::from(3i32));
493 assert_eq!(result2.max, Scalar::from(9i32));
494 Ok(())
495 }
496
497 #[test]
498 fn test_state_merge() -> VortexResult<()> {
499 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
500 let mut state = MinMax.empty_partial(&EmptyOptions, &dtype)?;
501
502 let struct_dtype = make_minmax_dtype(&dtype);
503 let scalar1 = Scalar::struct_(
504 struct_dtype.clone(),
505 vec![Scalar::from(5i32), Scalar::from(15i32)],
506 );
507 MinMax.combine_partials(&mut state, scalar1)?;
508
509 let scalar2 = Scalar::struct_(struct_dtype, vec![Scalar::from(2i32), Scalar::from(10i32)]);
510 MinMax.combine_partials(&mut state, scalar2)?;
511
512 let result = MinMaxResult::from_scalar(MinMax.to_scalar(&state)?)?
513 .vortex_expect("should have result");
514 assert_eq!(result.min, Scalar::from(2i32));
515 assert_eq!(result.max, Scalar::from(15i32));
516 Ok(())
517 }
518
519 #[test]
520 fn test_constant_nan() -> VortexResult<()> {
521 let scalar = Scalar::primitive(f16::NAN, Nullability::NonNullable);
522 let array = ConstantArray::new(scalar, 2).into_array();
523 let mut ctx = LEGACY_SESSION.create_execution_ctx();
524 assert_eq!(min_max(&array, &mut ctx)?, None);
525 Ok(())
526 }
527
528 #[test]
529 fn test_chunked() -> VortexResult<()> {
530 let chunk1 = PrimitiveArray::from_option_iter([Some(5i32), None, Some(1)]);
531 let chunk2 = PrimitiveArray::from_option_iter([Some(10i32), Some(3), None]);
532 let dtype = chunk1.dtype().clone();
533 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
534 let mut ctx = LEGACY_SESSION.create_execution_ctx();
535 let result = min_max(&chunked.into_array(), &mut ctx)?.vortex_expect("should have result");
536 assert_eq!(result.min, Scalar::from(1i32));
537 assert_eq!(result.max, Scalar::from(10i32));
538 Ok(())
539 }
540
541 #[test]
542 fn test_all_null() -> VortexResult<()> {
543 let p = PrimitiveArray::from_option_iter::<i32, _>([None, None, None]);
544 let mut ctx = LEGACY_SESSION.create_execution_ctx();
545 assert_eq!(min_max(&p.into_array(), &mut ctx)?, None);
546 Ok(())
547 }
548
549 #[test]
550 fn test_varbin() -> VortexResult<()> {
551 let array = VarBinArray::from_iter(
552 vec![
553 Some("hello world"),
554 None,
555 Some("hello world this is a long string"),
556 None,
557 ],
558 DType::Utf8(Nullability::Nullable),
559 );
560 let mut ctx = LEGACY_SESSION.create_execution_ctx();
561 let result = min_max(&array.into_array(), &mut ctx)?.vortex_expect("should have result");
562 assert_eq!(
563 result.min,
564 Scalar::utf8("hello world", Nullability::NonNullable)
565 );
566 assert_eq!(
567 result.max,
568 Scalar::utf8(
569 "hello world this is a long string",
570 Nullability::NonNullable
571 )
572 );
573 Ok(())
574 }
575
576 #[test]
577 fn test_decimal() -> VortexResult<()> {
578 let decimal = DecimalArray::new(
579 buffer![100i32, 2000i32, 200i32],
580 DecimalDType::new(4, 2),
581 Validity::from_iter([true, false, true]),
582 );
583 let mut ctx = LEGACY_SESSION.create_execution_ctx();
584 let result = min_max(&decimal.into_array(), &mut ctx)?.vortex_expect("should have result");
585
586 let non_nullable_dtype = DType::Decimal(DecimalDType::new(4, 2), Nullability::NonNullable);
587 let expected_min = Scalar::try_new(
588 non_nullable_dtype.clone(),
589 Some(ScalarValue::from(DecimalValue::from(100i32))),
590 )?;
591 let expected_max = Scalar::try_new(
592 non_nullable_dtype,
593 Some(ScalarValue::from(DecimalValue::from(200i32))),
594 )?;
595 assert_eq!(result.min, expected_min);
596 assert_eq!(result.max, expected_max);
597 Ok(())
598 }
599
600 #[test]
601 fn list_and_fixed_size_list_return_dtype() {
602 let element_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
603 let list_dtype = DType::List(Arc::new(element_dtype.clone()), Nullability::Nullable);
604 let fixed_size_list_dtype =
605 DType::FixedSizeList(Arc::new(element_dtype), 1, Nullability::Nullable);
606
607 assert_eq!(
608 MinMax.return_dtype(&EmptyOptions, &list_dtype),
609 Some(make_minmax_dtype(&list_dtype))
610 );
611 assert_eq!(
612 MinMax.return_dtype(&EmptyOptions, &fixed_size_list_dtype),
613 Some(make_minmax_dtype(&fixed_size_list_dtype))
614 );
615 }
616
617 #[test]
618 fn list_and_fixed_size_list_min_max_returns_none() -> VortexResult<()> {
619 let mut ctx = LEGACY_SESSION.create_execution_ctx();
620
621 let list_array = ListArray::try_new(
622 buffer![1i32, 2, 3].into_array(),
623 buffer![0u32, 2, 3].into_array(),
624 Validity::NonNullable,
625 )?
626 .into_array();
627 assert_eq!(min_max(&list_array, &mut ctx)?, None);
628
629 let fixed_size_list_array = FixedSizeListArray::try_new(
630 buffer![1i32, 2, 3, 4].into_array(),
631 2,
632 Validity::NonNullable,
633 2,
634 )?
635 .into_array();
636 assert_eq!(min_max(&fixed_size_list_array, &mut ctx)?, None);
637
638 Ok(())
639 }
640
641 use crate::dtype::half::f16;
642
643 #[test]
644 fn test_bool_with_nulls() -> VortexResult<()> {
645 let mut ctx = LEGACY_SESSION.create_execution_ctx();
646
647 let result = min_max(
648 &BoolArray::from_iter(vec![Some(true), Some(true), None, None]).into_array(),
649 &mut ctx,
650 )?;
651 assert_eq!(
652 result,
653 Some(MinMaxResult {
654 min: Scalar::bool(true, Nullability::NonNullable),
655 max: Scalar::bool(true, Nullability::NonNullable),
656 })
657 );
658
659 let result = min_max(
660 &BoolArray::from_iter(vec![None, Some(true), Some(true)]).into_array(),
661 &mut ctx,
662 )?;
663 assert_eq!(
664 result,
665 Some(MinMaxResult {
666 min: Scalar::bool(true, Nullability::NonNullable),
667 max: Scalar::bool(true, Nullability::NonNullable),
668 })
669 );
670
671 let result = min_max(
672 &BoolArray::from_iter(vec![None, Some(true), Some(true), None]).into_array(),
673 &mut ctx,
674 )?;
675 assert_eq!(
676 result,
677 Some(MinMaxResult {
678 min: Scalar::bool(true, Nullability::NonNullable),
679 max: Scalar::bool(true, Nullability::NonNullable),
680 })
681 );
682
683 let result = min_max(
684 &BoolArray::from_iter(vec![Some(false), Some(false), None, None]).into_array(),
685 &mut ctx,
686 )?;
687 assert_eq!(
688 result,
689 Some(MinMaxResult {
690 min: Scalar::bool(false, Nullability::NonNullable),
691 max: Scalar::bool(false, Nullability::NonNullable),
692 })
693 );
694 Ok(())
695 }
696
697 #[test]
703 fn test_bool_chunked_with_empty_chunk() -> VortexResult<()> {
704 let mut ctx = LEGACY_SESSION.create_execution_ctx();
705
706 let empty = BoolArray::new(BitBuffer::from([].as_slice()), Validity::NonNullable);
707 let chunk1 = BoolArray::new(
708 BitBuffer::from([true, true].as_slice()),
709 Validity::NonNullable,
710 );
711 let chunk2 = BoolArray::new(
712 BitBuffer::from([true, true, true].as_slice()),
713 Validity::NonNullable,
714 );
715 let chunked = ChunkedArray::try_new(
716 vec![empty.into_array(), chunk1.into_array(), chunk2.into_array()],
717 DType::Bool(Nullability::NonNullable),
718 )?;
719
720 let result = min_max(&chunked.into_array(), &mut ctx)?;
721 assert_eq!(
722 result,
723 Some(MinMaxResult {
724 min: Scalar::bool(true, Nullability::NonNullable),
725 max: Scalar::bool(true, Nullability::NonNullable),
726 })
727 );
728 Ok(())
729 }
730
731 #[test]
738 fn test_chunked_with_empty_constant_chunk() -> VortexResult<()> {
739 let mut ctx = LEGACY_SESSION.create_execution_ctx();
740
741 let empty = ConstantArray::new(Scalar::primitive(u32::MAX, Nullability::NonNullable), 0)
742 .into_array();
743 let chunk1 = PrimitiveArray::new(buffer![7631471u32], Validity::NonNullable).into_array();
744 let chunk2 = PrimitiveArray::new(buffer![0u32], Validity::NonNullable).into_array();
745 let chunked = ChunkedArray::try_new(
746 vec![empty, chunk1, chunk2],
747 DType::Primitive(PType::U32, Nullability::NonNullable),
748 )?;
749
750 assert_eq!(
751 min_max(&chunked.into_array(), &mut ctx)?,
752 Some(MinMaxResult {
753 min: Scalar::primitive(0u32, Nullability::NonNullable),
754 max: Scalar::primitive(7631471u32, Nullability::NonNullable),
755 })
756 );
757 Ok(())
758 }
759
760 #[test]
761 fn test_varbin_all_nulls() -> VortexResult<()> {
762 let array = VarBinArray::from_iter(
763 vec![Option::<&str>::None, None, None],
764 DType::Utf8(Nullability::Nullable),
765 );
766 let mut ctx = LEGACY_SESSION.create_execution_ctx();
767 assert_eq!(min_max(&array.into_array(), &mut ctx)?, None);
768 Ok(())
769 }
770}