• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16593958537

29 Jul 2025 10:48AM UTC coverage: 82.285% (+0.5%) from 81.796%
16593958537

Pull #4036

github

web-flow
Merge 04147cb0f into 348079fc3
Pull Request #4036: varbinview builder buffer deduplication

146 of 154 new or added lines in 2 files covered. (94.81%)

348 existing lines in 26 files now uncovered.

44470 of 54044 relevant lines covered (82.28%)

169522.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.06
/vortex-array/src/compute/min_max.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::sync::LazyLock;
5

6
use arcref::ArcRef;
7
use vortex_dtype::{DType, Nullability, StructFields};
8
use vortex_error::{VortexExpect, VortexResult, vortex_bail};
9
use vortex_scalar::Scalar;
10

11
use crate::Array;
12
use crate::compute::{ComputeFn, ComputeFnVTable, InvocationArgs, Kernel, Output, UnaryArgs};
13
use crate::stats::{Precision, Stat, StatsProviderExt};
14
use crate::vtable::VTable;
15

16
static MIN_MAX_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
3,895✔
17
    let compute = ComputeFn::new("min_max".into(), ArcRef::new_ref(&MinMax));
3,895✔
18
    for kernel in inventory::iter::<MinMaxKernelRef> {
50,983✔
19
        compute.register_kernel(kernel.0.clone());
47,088✔
20
    }
47,088✔
21
    compute
3,895✔
22
});
3,895✔
23

24
/// The minimum and maximum non-null values of an array, or None if there are no non-null values.
25
///
26
/// The return value dtype is the non-nullable version of the array dtype.
27
///
28
/// This will update the stats set of this array (as a side effect).
29
pub fn min_max(array: &dyn Array) -> VortexResult<Option<MinMaxResult>> {
58,419✔
30
    let scalar = MIN_MAX_FN
58,419✔
31
        .invoke(&InvocationArgs {
58,419✔
32
            inputs: &[array.into()],
58,419✔
33
            options: &(),
58,419✔
34
        })?
58,419✔
35
        .unwrap_scalar()?;
58,419✔
36
    MinMaxResult::from_scalar(scalar)
58,419✔
37
}
58,419✔
38

39
#[derive(Debug, Clone, PartialEq, Eq)]
40
pub struct MinMaxResult {
41
    pub min: Scalar,
42
    pub max: Scalar,
43
}
44

45
impl MinMaxResult {
46
    pub fn from_scalar(scalar: Scalar) -> VortexResult<Option<Self>> {
115,866✔
47
        if scalar.is_null() {
115,866✔
48
            Ok(None)
11,282✔
49
        } else {
50
            let min = scalar
104,584✔
51
                .as_struct()
104,584✔
52
                .field_by_idx(0)
104,584✔
53
                .vortex_expect("missing min field");
104,584✔
54
            let max = scalar
104,584✔
55
                .as_struct()
104,584✔
56
                .field_by_idx(1)
104,584✔
57
                .vortex_expect("missing max field");
104,584✔
58
            Ok(Some(MinMaxResult { min, max }))
104,584✔
59
        }
60
    }
115,866✔
61
}
62

63
pub struct MinMax;
64

65
impl ComputeFnVTable for MinMax {
66
    fn invoke(
58,419✔
67
        &self,
58,419✔
68
        args: &InvocationArgs,
58,419✔
69
        kernels: &[ArcRef<dyn Kernel>],
58,419✔
70
    ) -> VortexResult<Output> {
58,419✔
71
        let UnaryArgs { array, .. } = UnaryArgs::<()>::try_from(args)?;
58,419✔
72

73
        let return_dtype = self.return_dtype(args)?;
58,419✔
74

75
        match min_max_impl(array, kernels)? {
58,419✔
76
            None => Ok(Scalar::null(return_dtype).into()),
5,870✔
77
            Some(MinMaxResult { min, max }) => {
52,549✔
78
                assert!(
52,549✔
79
                    min <= max,
52,549✔
UNCOV
80
                    "min > max: min={} max={} encoding={}",
×
81
                    min,
82
                    max,
UNCOV
83
                    array.encoding_id()
×
84
                );
85

86
                // Update the stats set with the computed min/max
87
                array
52,549✔
88
                    .statistics()
52,549✔
89
                    .set(Stat::Min, Precision::Exact(min.value().clone()));
52,549✔
90
                array
52,549✔
91
                    .statistics()
52,549✔
92
                    .set(Stat::Max, Precision::Exact(max.value().clone()));
52,549✔
93

94
                // Return the min/max as a struct scalar
95
                Ok(Scalar::struct_(return_dtype, vec![min, max]).into())
52,549✔
96
            }
97
        }
98
    }
58,419✔
99

100
    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
116,838✔
101
        let UnaryArgs { array, .. } = UnaryArgs::<()>::try_from(args)?;
116,838✔
102

103
        // We return a min/max struct scalar, where the overall struct is nullable in the case
104
        // that the array is all null or empty.
105
        Ok(DType::Struct(
116,838✔
106
            StructFields::new(
116,838✔
107
                ["min", "max"].into(),
116,838✔
108
                vec![array.dtype().clone(), array.dtype().clone()],
116,838✔
109
            ),
116,838✔
110
            Nullability::Nullable,
116,838✔
111
        ))
116,838✔
112
    }
116,838✔
113

114
    fn return_len(&self, _args: &InvocationArgs) -> VortexResult<usize> {
58,419✔
115
        Ok(1)
58,419✔
116
    }
58,419✔
117

118
    fn is_elementwise(&self) -> bool {
58,419✔
119
        false
58,419✔
120
    }
58,419✔
121
}
122

123
fn min_max_impl(
58,419✔
124
    array: &dyn Array,
58,419✔
125
    kernels: &[ArcRef<dyn Kernel>],
58,419✔
126
) -> VortexResult<Option<MinMaxResult>> {
58,419✔
127
    if array.is_empty() || array.valid_count()? == 0 {
58,419✔
128
        return Ok(None);
458✔
129
    }
57,961✔
130

131
    let min = array
57,961✔
132
        .statistics()
57,961✔
133
        .get_scalar(Stat::Min, array.dtype())
57,961✔
134
        .and_then(Precision::as_exact);
57,961✔
135
    let max = array
57,961✔
136
        .statistics()
57,961✔
137
        .get_scalar(Stat::Max, array.dtype())
57,961✔
138
        .and_then(Precision::as_exact);
57,961✔
139

140
    if let Some((min, max)) = min.zip(max) {
57,961✔
141
        return Ok(Some(MinMaxResult { min, max }));
438✔
142
    }
57,523✔
143

144
    let args = InvocationArgs {
57,523✔
145
        inputs: &[array.into()],
57,523✔
146
        options: &(),
57,523✔
147
    };
57,523✔
148
    for kernel in kernels {
205,384✔
149
        if let Some(output) = kernel.invoke(&args)? {
205,308✔
150
            return MinMaxResult::from_scalar(output.unwrap_scalar()?);
57,447✔
151
        }
147,861✔
152
    }
153
    if let Some(output) = array.invoke(&MIN_MAX_FN, &args)? {
76✔
154
        return MinMaxResult::from_scalar(output.unwrap_scalar()?);
×
155
    }
76✔
156

157
    if !array.is_canonical() {
76✔
158
        let array = array.to_canonical()?;
76✔
159
        return min_max(array.as_ref());
76✔
UNCOV
160
    }
×
161

UNCOV
162
    vortex_bail!(NotImplemented: "min_max", array.encoding_id());
×
163
}
58,419✔
164

165
/// The minimum and maximum non-null values of an array, or None if there are no non-null values.
166
pub trait MinMaxKernel: VTable {
167
    fn min_max(&self, array: &Self::Array) -> VortexResult<Option<MinMaxResult>>;
168
}
169

170
pub struct MinMaxKernelRef(ArcRef<dyn Kernel>);
171
inventory::collect!(MinMaxKernelRef);
172

173
#[derive(Debug)]
174
pub struct MinMaxKernelAdapter<V: VTable>(pub V);
175

176
impl<V: VTable + MinMaxKernel> MinMaxKernelAdapter<V> {
UNCOV
177
    pub const fn lift(&'static self) -> MinMaxKernelRef {
×
UNCOV
178
        MinMaxKernelRef(ArcRef::new_ref(self))
×
UNCOV
179
    }
×
180
}
181

182
impl<V: VTable + MinMaxKernel> Kernel for MinMaxKernelAdapter<V> {
183
    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
205,226✔
184
        let inputs = UnaryArgs::<()>::try_from(args)?;
205,226✔
185
        let Some(array) = inputs.array.as_opt::<V>() else {
205,226✔
186
            return Ok(None);
147,816✔
187
        };
188
        let dtype = DType::Struct(
57,410✔
189
            StructFields::new(
57,410✔
190
                ["min", "max"].into(),
57,410✔
191
                vec![array.dtype().clone(), array.dtype().clone()],
57,410✔
192
            ),
57,410✔
193
            Nullability::Nullable,
57,410✔
194
        );
57,410✔
195
        Ok(Some(match V::min_max(&self.0, array)? {
57,410✔
196
            None => Scalar::null(dtype).into(),
5,412✔
197
            Some(MinMaxResult { min, max }) => Scalar::struct_(dtype, vec![min, max]).into(),
51,998✔
198
        }))
199
    }
205,226✔
200
}
201

202
#[cfg(test)]
203
mod tests {
204
    use arrow_buffer::BooleanBuffer;
205
    use vortex_buffer::buffer;
206

207
    use crate::arrays::{BoolArray, NullArray, PrimitiveArray};
208
    use crate::compute::{MinMaxResult, min_max};
209
    use crate::validity::Validity;
210

211
    #[test]
212
    fn test_prim_max() {
1✔
213
        let p = PrimitiveArray::new(buffer![1, 2, 3], Validity::NonNullable);
1✔
214
        assert_eq!(
1✔
215
            min_max(p.as_ref()).unwrap(),
1✔
216
            Some(MinMaxResult {
1✔
217
                min: 1.into(),
1✔
218
                max: 3.into()
1✔
219
            })
1✔
220
        );
221
    }
1✔
222

223
    #[test]
224
    fn test_bool_max() {
1✔
225
        let p = BoolArray::new(
1✔
226
            BooleanBuffer::from([true, true, true].as_slice()),
1✔
227
            Validity::NonNullable,
1✔
228
        );
229
        assert_eq!(
1✔
230
            min_max(p.as_ref()).unwrap(),
1✔
231
            Some(MinMaxResult {
1✔
232
                min: true.into(),
1✔
233
                max: true.into()
1✔
234
            })
1✔
235
        );
236

237
        let p = BoolArray::new(
1✔
238
            BooleanBuffer::from([false, false, false].as_slice()),
1✔
239
            Validity::NonNullable,
1✔
240
        );
241
        assert_eq!(
1✔
242
            min_max(p.as_ref()).unwrap(),
1✔
243
            Some(MinMaxResult {
1✔
244
                min: false.into(),
1✔
245
                max: false.into()
1✔
246
            })
1✔
247
        );
248

249
        let p = BoolArray::new(
1✔
250
            BooleanBuffer::from([false, true, false].as_slice()),
1✔
251
            Validity::NonNullable,
1✔
252
        );
253
        assert_eq!(
1✔
254
            min_max(p.as_ref()).unwrap(),
1✔
255
            Some(MinMaxResult {
1✔
256
                min: false.into(),
1✔
257
                max: true.into()
1✔
258
            })
1✔
259
        );
260
    }
1✔
261

262
    #[test]
263
    fn test_null() {
1✔
264
        let p = NullArray::new(1);
1✔
265
        assert_eq!(min_max(p.as_ref()).unwrap(), None);
1✔
266
    }
1✔
267
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc