Skip to main content

datafusion_spark/function/math/
width_bucket.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use arrow::array::{
21    Array, ArrayRef, DurationMicrosecondArray, Float64Array, IntervalMonthDayNanoArray,
22    IntervalYearMonthArray,
23};
24use arrow::datatypes::DataType::{self, Duration, Float64, Int64, Interval};
25use arrow::datatypes::IntervalUnit::{MonthDayNano, YearMonth};
26use datafusion_common::cast::{
27    as_duration_microsecond_array, as_float64_array, as_int64_array,
28    as_interval_mdn_array, as_interval_ym_array,
29};
30use datafusion_common::types::{
31    NativeType, logical_duration_microsecond, logical_float64, logical_int64,
32    logical_interval_mdn, logical_interval_year_month,
33};
34use datafusion_common::{Result, exec_err, internal_err};
35use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
36use datafusion_expr::{
37    ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature,
38    TypeSignatureClass,
39};
40use datafusion_functions::utils::make_scalar_function;
41
42use arrow::array::{Int64Array, Int64Builder};
43use arrow::datatypes::TimeUnit::Microsecond;
44use datafusion_expr::Coercion;
45use datafusion_expr::Volatility::Immutable;
46
47#[derive(Debug, PartialEq, Eq, Hash)]
48pub struct SparkWidthBucket {
49    signature: Signature,
50}
51
52impl Default for SparkWidthBucket {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl SparkWidthBucket {
59    pub fn new() -> Self {
60        let numeric = Coercion::new_implicit(
61            TypeSignatureClass::Native(logical_float64()),
62            vec![TypeSignatureClass::Numeric],
63            NativeType::Float64,
64        );
65        let duration = Coercion::new_implicit(
66            TypeSignatureClass::Native(logical_duration_microsecond()),
67            vec![TypeSignatureClass::Duration],
68            NativeType::Duration(Microsecond),
69        );
70        let interval_ym = Coercion::new_exact(TypeSignatureClass::Native(
71            logical_interval_year_month(),
72        ));
73        let interval_mdn =
74            Coercion::new_exact(TypeSignatureClass::Native(logical_interval_mdn()));
75        let bucket = Coercion::new_implicit(
76            TypeSignatureClass::Native(logical_int64()),
77            vec![TypeSignatureClass::Integer],
78            NativeType::Int64,
79        );
80        let type_signature = Signature::one_of(
81            vec![
82                TypeSignature::Coercible(vec![
83                    numeric.clone(),
84                    numeric.clone(),
85                    numeric.clone(),
86                    bucket.clone(),
87                ]),
88                TypeSignature::Coercible(vec![
89                    duration.clone(),
90                    duration.clone(),
91                    duration.clone(),
92                    bucket.clone(),
93                ]),
94                TypeSignature::Coercible(vec![
95                    interval_ym.clone(),
96                    interval_ym.clone(),
97                    interval_ym.clone(),
98                    bucket.clone(),
99                ]),
100                TypeSignature::Coercible(vec![
101                    interval_mdn.clone(),
102                    interval_mdn.clone(),
103                    interval_mdn.clone(),
104                    bucket.clone(),
105                ]),
106            ],
107            Immutable,
108        )
109        .with_parameter_names(vec!["expr", "min", "max", "num_buckets"])
110        .expect("valid parameter names");
111        Self {
112            signature: type_signature,
113        }
114    }
115}
116
117impl ScalarUDFImpl for SparkWidthBucket {
118    fn name(&self) -> &str {
119        "width_bucket"
120    }
121
122    fn signature(&self) -> &Signature {
123        &self.signature
124    }
125
126    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
127        Ok(Int64)
128    }
129
130    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
131        make_scalar_function(width_bucket_kern, vec![])(&args.args)
132    }
133
134    fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
135        if input.len() == 1 {
136            let value = &input[0];
137            Ok(value.sort_properties)
138        } else {
139            Ok(SortProperties::default())
140        }
141    }
142}
143
144fn width_bucket_kern(args: &[ArrayRef]) -> Result<ArrayRef> {
145    let [v, minv, maxv, nb] = args else {
146        return exec_err!(
147            "width_bucket expects exactly 4 argument, got {}",
148            args.len()
149        );
150    };
151
152    match v.data_type() {
153        Float64 => {
154            let v = as_float64_array(v)?;
155            let min = as_float64_array(minv)?;
156            let max = as_float64_array(maxv)?;
157            let n_bucket = as_int64_array(nb)?;
158            Ok(Arc::new(width_bucket_float64(v, min, max, n_bucket)))
159        }
160        Duration(Microsecond) => {
161            let v = as_duration_microsecond_array(v)?;
162            let min = as_duration_microsecond_array(minv)?;
163            let max = as_duration_microsecond_array(maxv)?;
164            let n_bucket = as_int64_array(nb)?;
165            Ok(Arc::new(width_bucket_i64_as_float(v, min, max, n_bucket)))
166        }
167        Interval(YearMonth) => {
168            let v = as_interval_ym_array(v)?;
169            let min = as_interval_ym_array(minv)?;
170            let max = as_interval_ym_array(maxv)?;
171            let n_bucket = as_int64_array(nb)?;
172            Ok(Arc::new(width_bucket_i32_as_float(v, min, max, n_bucket)))
173        }
174        Interval(MonthDayNano) => {
175            let v = as_interval_mdn_array(v)?;
176            let min = as_interval_mdn_array(minv)?;
177            let max = as_interval_mdn_array(maxv)?;
178            let n_bucket = as_int64_array(nb)?;
179            Ok(Arc::new(width_bucket_interval_mdn_exact(
180                v, min, max, n_bucket,
181            )))
182        }
183
184        other => internal_err!(
185            "width_bucket received unexpected data types: {:?}, {:?}, {:?}, {:?}",
186            other,
187            minv.data_type(),
188            maxv.data_type(),
189            nb.data_type()
190        ),
191    }
192}
193
194macro_rules! width_bucket_kernel_impl {
195    ($name:ident, $arr_ty:ty, $to_f64:expr, $check_nan:expr) => {
196        pub(crate) fn $name(
197            v: &$arr_ty,
198            min: &$arr_ty,
199            max: &$arr_ty,
200            n_bucket: &Int64Array,
201        ) -> Int64Array {
202            let len = v.len();
203            let mut b = Int64Builder::with_capacity(len);
204
205            for i in 0..len {
206                if v.is_null(i) || min.is_null(i) || max.is_null(i) || n_bucket.is_null(i)
207                {
208                    b.append_null();
209                    continue;
210                }
211                let x = ($to_f64)(v, i);
212                let l = ($to_f64)(min, i);
213                let h = ($to_f64)(max, i);
214                let buckets = n_bucket.value(i);
215
216                if buckets <= 0 {
217                    b.append_null();
218                    continue;
219                }
220                let next_bucket = (buckets + 1) as i64;
221                if $check_nan {
222                    if !x.is_finite() || !l.is_finite() || !h.is_finite() {
223                        b.append_null();
224                        continue;
225                    }
226                }
227
228                let ord = match l.partial_cmp(&h) {
229                    Some(o) => o,
230                    None => {
231                        b.append_null();
232                        continue;
233                    }
234                };
235                if ord == std::cmp::Ordering::Equal {
236                    b.append_null();
237                    continue;
238                }
239                let asc = ord == std::cmp::Ordering::Less;
240
241                if asc {
242                    if x < l {
243                        b.append_value(0);
244                        continue;
245                    }
246                    if x >= h {
247                        b.append_value(next_bucket);
248                        continue;
249                    }
250                } else {
251                    if x > l {
252                        b.append_value(0);
253                        continue;
254                    }
255                    if x <= h {
256                        b.append_value(next_bucket);
257                        continue;
258                    }
259                }
260
261                let width = (h - l) / (buckets as f64);
262                if width == 0.0 || !width.is_finite() {
263                    b.append_null();
264                    continue;
265                }
266                let mut bucket = ((x - l) / width).floor() as i64 + 1;
267                if bucket < 1 {
268                    bucket = 1;
269                }
270                if bucket > next_bucket {
271                    bucket = next_bucket;
272                }
273
274                b.append_value(bucket);
275            }
276
277            b.finish()
278        }
279    };
280}
281
282width_bucket_kernel_impl!(
283    width_bucket_float64,
284    Float64Array,
285    |arr: &Float64Array, i: usize| arr.value(i),
286    true
287);
288
289width_bucket_kernel_impl!(
290    width_bucket_i64_as_float,
291    DurationMicrosecondArray,
292    |arr: &DurationMicrosecondArray, i: usize| arr.value(i) as f64,
293    false
294);
295
296width_bucket_kernel_impl!(
297    width_bucket_i32_as_float,
298    IntervalYearMonthArray,
299    |arr: &IntervalYearMonthArray, i: usize| arr.value(i) as f64,
300    false
301);
302const NS_PER_DAY_I128: i128 = 86_400_000_000_000;
303pub(crate) fn width_bucket_interval_mdn_exact(
304    v: &IntervalMonthDayNanoArray,
305    lo: &IntervalMonthDayNanoArray,
306    hi: &IntervalMonthDayNanoArray,
307    n: &Int64Array,
308) -> Int64Array {
309    let len = v.len();
310    let mut b = Int64Builder::with_capacity(len);
311
312    for i in 0..len {
313        if v.is_null(i) || lo.is_null(i) || hi.is_null(i) || n.is_null(i) {
314            b.append_null();
315            continue;
316        }
317        let buckets = n.value(i);
318        if buckets <= 0 {
319            b.append_null();
320            continue;
321        }
322        let next_bucket = buckets + 1;
323
324        let x = v.value(i);
325        let l = lo.value(i);
326        let h = hi.value(i);
327
328        // asc/desc
329        // Values of IntervalMonthDayNano are compared using their binary representation, which can lead to surprising results.
330        let asc = (l.months, l.days, l.nanoseconds) < (h.months, h.days, h.nanoseconds);
331        if (l.months, l.days, l.nanoseconds) == (h.months, h.days, h.nanoseconds) {
332            b.append_null();
333            continue;
334        }
335
336        // ------------------- only month -------------------
337        if l.days == h.days && l.nanoseconds == h.nanoseconds && l.months != h.months {
338            let x_m = x.months as f64;
339            let l_m = l.months as f64;
340            let h_m = h.months as f64;
341
342            if asc {
343                if x_m < l_m {
344                    b.append_value(0);
345                    continue;
346                }
347                if x_m >= h_m {
348                    b.append_value(next_bucket);
349                    continue;
350                }
351            } else {
352                if x_m > l_m {
353                    b.append_value(0);
354                    continue;
355                }
356                if x_m <= h_m {
357                    b.append_value(next_bucket);
358                    continue;
359                }
360            }
361
362            let width = (h_m - l_m) / (buckets as f64);
363            if width == 0.0 || !width.is_finite() {
364                b.append_null();
365                continue;
366            }
367
368            let mut bucket = ((x_m - l_m) / width).floor() as i64 + 1;
369            if bucket < 1 {
370                bucket = 1;
371            }
372            if bucket > next_bucket {
373                bucket = next_bucket;
374            }
375            b.append_value(bucket);
376            continue;
377        }
378
379        // ---------------  months equals -------------------
380        if l.months == h.months {
381            let base_days = l.days as i128;
382            let base_ns = l.nanoseconds as i128;
383
384            let xf = (x.days as i128 - base_days) * NS_PER_DAY_I128
385                + (x.nanoseconds as i128 - base_ns);
386            let hf = (h.days as i128 - base_days) * NS_PER_DAY_I128
387                + (h.nanoseconds as i128 - base_ns);
388
389            let x_f = xf as f64;
390            let l_f = 0.0;
391            let h_f = hf as f64;
392
393            if asc {
394                if x_f < l_f {
395                    b.append_value(0);
396                    continue;
397                }
398                if x_f >= h_f {
399                    b.append_value(next_bucket);
400                    continue;
401                }
402            } else {
403                if x_f > l_f {
404                    b.append_value(0);
405                    continue;
406                }
407                if x_f <= h_f {
408                    b.append_value(next_bucket);
409                    continue;
410                }
411            }
412
413            let width = (h_f - l_f) / (buckets as f64);
414            if width == 0.0 || !width.is_finite() {
415                b.append_null();
416                continue;
417            }
418
419            let mut bucket = ((x_f - l_f) / width).floor() as i64 + 1;
420            if bucket < 1 {
421                bucket = 1;
422            }
423            if bucket > next_bucket {
424                bucket = next_bucket;
425            }
426            b.append_value(bucket);
427            continue;
428        }
429
430        b.append_null();
431    }
432
433    b.finish()
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use arrow::datatypes::Int64Type;
440
441    use arrow::array::{
442        ArrayRef, AsArray, DurationMicrosecondArray, Float64Array, Int32Array,
443        Int64Array, IntervalYearMonthArray,
444    };
445    use arrow::datatypes::IntervalMonthDayNano;
446
447    // --- Helpers -------------------------------------------------------------
448
449    fn i64_array_all(len: usize, val: i64) -> Arc<Int64Array> {
450        Arc::new(Int64Array::from(vec![val; len]))
451    }
452
453    fn f64_array(vals: &[f64]) -> Arc<Float64Array> {
454        Arc::new(Float64Array::from(vals.to_vec()))
455    }
456
457    fn f64_array_opt(vals: &[Option<f64>]) -> Arc<Float64Array> {
458        Arc::new(Float64Array::from(vals.to_vec()))
459    }
460
461    fn dur_us_array(vals: &[i64]) -> Arc<DurationMicrosecondArray> {
462        Arc::new(DurationMicrosecondArray::from(vals.to_vec()))
463    }
464
465    fn ym_array(vals: &[i32]) -> Arc<IntervalYearMonthArray> {
466        Arc::new(IntervalYearMonthArray::from(vals.to_vec()))
467    }
468
469    fn mdn_array(vals: &[(i32, i32, i64)]) -> Arc<IntervalMonthDayNanoArray> {
470        let data: Vec<IntervalMonthDayNano> = vals
471            .iter()
472            .map(|(m, d, ns)| IntervalMonthDayNano::new(*m, *d, *ns))
473            .collect();
474        Arc::new(IntervalMonthDayNanoArray::from(data))
475    }
476
477    // --- Float64 -------------------------------------------------------------
478
479    #[test]
480    fn test_width_bucket_f64_basic() {
481        let v = f64_array(&[0.5, 1.0, 9.9, -1.0, 10.0]);
482        let lo = f64_array(&[0.0, 0.0, 0.0, 0.0, 0.0]);
483        let hi = f64_array(&[10.0, 10.0, 10.0, 10.0, 10.0]);
484        let n = i64_array_all(5, 10);
485
486        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
487        let out = out.as_primitive::<Int64Type>();
488        assert_eq!(out.values(), &[1, 2, 10, 0, 11]);
489    }
490
491    #[test]
492    fn test_width_bucket_f64_descending_range() {
493        let v = f64_array(&[9.9, 10.0, 0.0, -0.1, 10.1]);
494        let lo = f64_array(&[10.0; 5]);
495        let hi = f64_array(&[0.0; 5]);
496        let n = i64_array_all(5, 10);
497
498        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
499        let out = out.as_primitive::<Int64Type>();
500
501        assert_eq!(out.values(), &[1, 1, 11, 11, 0]);
502    }
503    #[test]
504    fn test_width_bucket_f64_bounds_inclusive_exclusive_asc() {
505        let v = f64_array(&[0.0, 9.999999999, 10.0]);
506        let lo = f64_array(&[0.0; 3]);
507        let hi = f64_array(&[10.0; 3]);
508        let n = i64_array_all(3, 10);
509
510        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
511        let out = out.as_primitive::<Int64Type>();
512        assert_eq!(out.values(), &[1, 10, 11]);
513    }
514
515    #[test]
516    fn test_width_bucket_f64_bounds_inclusive_exclusive_desc() {
517        let v = f64_array(&[10.0, 0.0, -0.000001]);
518        let lo = f64_array(&[10.0; 3]);
519        let hi = f64_array(&[0.0; 3]);
520        let n = i64_array_all(3, 10);
521
522        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
523        let out = out.as_primitive::<Int64Type>();
524        assert_eq!(out.values(), &[1, 11, 11]);
525    }
526
527    #[test]
528    fn test_width_bucket_f64_edge_cases() {
529        let v = f64_array(&[1.0, 5.0, 9.0]);
530        let lo = f64_array(&[0.0, 0.0, 0.0]);
531        let hi = f64_array(&[10.0, 10.0, 10.0]);
532        let n = Arc::new(Int64Array::from(vec![0, -1, 10]));
533        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
534        let out = out.as_primitive::<Int64Type>();
535        assert!(out.is_null(0));
536        assert!(out.is_null(1));
537        assert_eq!(out.value(2), 10);
538
539        let v = f64_array(&[1.0]);
540        let lo = f64_array(&[5.0]);
541        let hi = f64_array(&[5.0]);
542        let n = i64_array_all(1, 10);
543        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
544        let out = out.as_primitive::<Int64Type>();
545        assert!(out.is_null(0));
546
547        let v = f64_array_opt(&[Some(f64::NAN)]);
548        let lo = f64_array(&[0.0]);
549        let hi = f64_array(&[10.0]);
550        let n = i64_array_all(1, 10);
551        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
552        let out = out.as_primitive::<Int64Type>();
553        assert!(out.is_null(0));
554    }
555
556    #[test]
557    fn test_width_bucket_f64_nulls_propagate() {
558        let v = f64_array_opt(&[None, Some(1.0), Some(2.0), Some(3.0)]);
559        let lo = f64_array(&[0.0; 4]);
560        let hi = f64_array(&[10.0; 4]);
561        let n = i64_array_all(4, 10);
562
563        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
564        let out = out.as_primitive::<Int64Type>();
565        assert!(out.is_null(0));
566        assert_eq!(out.value(1), 2);
567        assert_eq!(out.value(2), 3);
568        assert_eq!(out.value(3), 4);
569
570        let v = f64_array(&[1.0]);
571        let lo = f64_array_opt(&[None]);
572        let hi = f64_array(&[10.0]);
573        let n = i64_array_all(1, 10);
574        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
575        let out = out.as_primitive::<Int64Type>();
576        assert!(out.is_null(0));
577    }
578
579    // --- Duration(Microsecond) ----------------------------------------------
580
581    #[test]
582    fn test_width_bucket_duration_us() {
583        let v = dur_us_array(&[1_000_000, 0, -1]);
584        let lo = dur_us_array(&[0, 0, 0]);
585        let hi = dur_us_array(&[2_000_000, 2_000_000, 2_000_000]);
586        let n = i64_array_all(3, 2);
587
588        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
589        let out = out.as_primitive::<Int64Type>();
590        assert_eq!(out.values(), &[2, 1, 0]);
591    }
592
593    #[test]
594    fn test_width_bucket_duration_us_equal_bounds() {
595        let v = dur_us_array(&[0]);
596        let lo = dur_us_array(&[1]);
597        let hi = dur_us_array(&[1]);
598        let n = i64_array_all(1, 10);
599        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
600        assert!(out.as_primitive::<Int64Type>().is_null(0));
601    }
602
603    // --- Interval(YearMonth) ------------------------------------------------
604
605    #[test]
606    fn test_width_bucket_interval_ym_basic() {
607        let v = ym_array(&[0, 5, 11, 12, 13]);
608        let lo = ym_array(&[0; 5]);
609        let hi = ym_array(&[12; 5]);
610        let n = i64_array_all(5, 12);
611
612        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
613        let out = out.as_primitive::<Int64Type>();
614        assert_eq!(out.values(), &[1, 6, 12, 13, 13]);
615    }
616
617    #[test]
618    fn test_width_bucket_interval_ym_desc() {
619        let v = ym_array(&[11, 12, 0, -1, 13]);
620        let lo = ym_array(&[12; 5]);
621        let hi = ym_array(&[0; 5]);
622        let n = i64_array_all(5, 12);
623
624        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
625        let out = out.as_primitive::<Int64Type>();
626        assert_eq!(out.values(), &[2, 1, 13, 13, 0]);
627    }
628
629    // --- Interval(MonthDayNano) --------------------------------------------
630
631    #[test]
632    fn test_width_bucket_interval_mdn_months_only_basic() {
633        let v = mdn_array(&[(0, 0, 0), (5, 0, 0), (11, 0, 0), (12, 0, 0), (13, 0, 0)]);
634        let lo = mdn_array(&[(0, 0, 0); 5]);
635        let hi = mdn_array(&[(12, 0, 0); 5]);
636        let n = i64_array_all(5, 12);
637
638        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
639        let out = out.as_primitive::<Int64Type>();
640        assert_eq!(out.values(), &[1, 6, 12, 13, 13]);
641    }
642
643    #[test]
644    fn test_width_bucket_interval_mdn_months_only_desc() {
645        let v = mdn_array(&[(11, 0, 0), (12, 0, 0), (0, 0, 0), (-1, 0, 0), (13, 0, 0)]);
646        let lo = mdn_array(&[(12, 0, 0); 5]);
647        let hi = mdn_array(&[(0, 0, 0); 5]);
648        let n = i64_array_all(5, 12);
649
650        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
651        let out = out.as_primitive::<Int64Type>();
652        // Mismo patrĂ³n que YM descendente
653        assert_eq!(out.values(), &[2, 1, 13, 13, 0]);
654    }
655
656    #[test]
657    fn test_width_bucket_interval_mdn_day_nano_basic() {
658        let v = mdn_array(&[
659            (0, 0, 0),
660            (0, 5, 0),
661            (0, 9, 0),
662            (0, 10, 0),
663            (0, -1, 0),
664            (0, 11, 0),
665        ]);
666        let lo = mdn_array(&[(0, 0, 0); 6]);
667        let hi = mdn_array(&[(0, 10, 0); 6]);
668        let n = i64_array_all(6, 10);
669
670        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
671        let out = out.as_primitive::<Int64Type>();
672        // x==hi -> n+1, x<lo -> 0, x>hi -> n+1
673        assert_eq!(out.values(), &[1, 6, 10, 11, 0, 11]);
674    }
675
676    #[test]
677    fn test_width_bucket_interval_mdn_day_nano_desc() {
678        let v = mdn_array(&[(0, 9, 0), (0, 10, 0), (0, 0, 0), (0, -1, 0), (0, 11, 0)]);
679        let lo = mdn_array(&[(0, 10, 0); 5]);
680        let hi = mdn_array(&[(0, 0, 0); 5]);
681        let n = i64_array_all(5, 10);
682
683        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
684        let out = out.as_primitive::<Int64Type>();
685
686        assert_eq!(out.values(), &[2, 1, 11, 11, 0]);
687    }
688    #[test]
689    fn test_width_bucket_interval_mdn_day_nano_desc_inside() {
690        let v = mdn_array(&[(0, 9, 1), (0, 10, 0), (0, 0, 0), (0, -1, 0), (0, 11, 0)]);
691        let lo = mdn_array(&[(0, 10, 0); 5]);
692        let hi = mdn_array(&[(0, 0, 0); 5]);
693        let n = i64_array_all(5, 10);
694
695        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
696        let out = out.as_primitive::<Int64Type>();
697
698        assert_eq!(out.values(), &[1, 1, 11, 11, 0]);
699    }
700
701    #[test]
702    fn test_width_bucket_interval_mdn_mixed_months_and_days_is_null() {
703        let v = mdn_array(&[(0, 1, 0)]);
704        let lo = mdn_array(&[(0, 0, 0)]);
705        let hi = mdn_array(&[(1, 1, 0)]);
706        let n = i64_array_all(1, 4);
707
708        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
709        let out = out.as_primitive::<Int64Type>();
710        assert!(out.is_null(0));
711    }
712
713    #[test]
714    fn test_width_bucket_interval_mdn_equal_bounds_is_null() {
715        let v = mdn_array(&[(0, 0, 0)]);
716        let lo = mdn_array(&[(1, 2, 3)]);
717        let hi = mdn_array(&[(1, 2, 3)]); // lo == hi
718        let n = i64_array_all(1, 10);
719
720        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
721        assert!(out.as_primitive::<Int64Type>().is_null(0));
722    }
723
724    #[test]
725    fn test_width_bucket_interval_mdn_invalid_n_is_null() {
726        let v = mdn_array(&[(0, 0, 0)]);
727        let lo = mdn_array(&[(0, 0, 0)]);
728        let hi = mdn_array(&[(0, 10, 0)]);
729        let n = Arc::new(Int64Array::from(vec![0])); // n <= 0
730
731        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
732        assert!(out.as_primitive::<Int64Type>().is_null(0));
733    }
734
735    #[test]
736    fn test_width_bucket_interval_mdn_nulls_propagate() {
737        let v = Arc::new(IntervalMonthDayNanoArray::from(vec![
738            None,
739            Some(IntervalMonthDayNano::new(0, 5, 0)),
740        ]));
741        let lo = mdn_array(&[(0, 0, 0), (0, 0, 0)]);
742        let hi = mdn_array(&[(0, 10, 0), (0, 10, 0)]);
743        let n = i64_array_all(2, 10);
744
745        let out = width_bucket_kern(&[v, lo, hi, n]).unwrap();
746        let out = out.as_primitive::<Int64Type>();
747        assert!(out.is_null(0));
748        assert_eq!(out.value(1), 6);
749    }
750
751    // --- Errores -------------------------------------------------------------
752
753    #[test]
754    fn test_width_bucket_wrong_arg_count() {
755        let v = f64_array(&[1.0]);
756        let lo = f64_array(&[0.0]);
757        let hi = f64_array(&[10.0]);
758        let err = width_bucket_kern(&[v, lo, hi]).unwrap_err();
759        let msg = format!("{err}");
760        assert!(msg.contains("expects exactly 4"), "unexpected error: {msg}");
761    }
762
763    #[test]
764    fn test_width_bucket_unsupported_type() {
765        let v: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
766        let lo = f64_array(&[0.0, 0.0, 0.0]);
767        let hi = f64_array(&[10.0, 10.0, 10.0]);
768        let n = i64_array_all(3, 10);
769
770        let err = width_bucket_kern(&[v, lo, hi, n]).unwrap_err();
771        let msg = format!("{err}");
772        assert!(
773            msg.contains("width_bucket received unexpected data types"),
774            "unexpected error: {msg}"
775        );
776    }
777}