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

vortex-data / vortex / 16595455362

29 Jul 2025 12:00PM UTC coverage: 82.182% (+0.4%) from 81.796%
16595455362

Pull #4036

github

web-flow
Merge 28b120788 into 261aabd6a
Pull Request #4036: varbinview builder buffer deduplication

147 of 155 new or added lines in 2 files covered. (94.84%)

404 existing lines in 34 files now uncovered.

44415 of 54045 relevant lines covered (82.18%)

167869.47 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,149✔
30
    let scalar = MIN_MAX_FN
58,149✔
31
        .invoke(&InvocationArgs {
58,149✔
32
            inputs: &[array.into()],
58,149✔
33
            options: &(),
58,149✔
34
        })?
58,149✔
35
        .unwrap_scalar()?;
58,149✔
36
    MinMaxResult::from_scalar(scalar)
58,149✔
37
}
58,149✔
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,328✔
47
        if scalar.is_null() {
115,328✔
48
            Ok(None)
11,282✔
49
        } else {
50
            let min = scalar
104,046✔
51
                .as_struct()
104,046✔
52
                .field_by_idx(0)
104,046✔
53
                .vortex_expect("missing min field");
104,046✔
54
            let max = scalar
104,046✔
55
                .as_struct()
104,046✔
56
                .field_by_idx(1)
104,046✔
57
                .vortex_expect("missing max field");
104,046✔
58
            Ok(Some(MinMaxResult { min, max }))
104,046✔
59
        }
60
    }
115,328✔
61
}
62

63
pub struct MinMax;
64

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

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

75
        match min_max_impl(array, kernels)? {
58,149✔
76
            None => Ok(Scalar::null(return_dtype).into()),
5,870✔
77
            Some(MinMaxResult { min, max }) => {
52,279✔
78
                assert!(
52,279✔
79
                    min <= max,
52,279✔
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,279✔
88
                    .statistics()
52,279✔
89
                    .set(Stat::Min, Precision::Exact(min.value().clone()));
52,279✔
90
                array
52,279✔
91
                    .statistics()
52,279✔
92
                    .set(Stat::Max, Precision::Exact(max.value().clone()));
52,279✔
93

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

100
    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
116,298✔
101
        let UnaryArgs { array, .. } = UnaryArgs::<()>::try_from(args)?;
116,298✔
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,298✔
106
            StructFields::new(
116,298✔
107
                ["min", "max"].into(),
116,298✔
108
                vec![array.dtype().clone(), array.dtype().clone()],
116,298✔
109
            ),
116,298✔
110
            Nullability::Nullable,
116,298✔
111
        ))
116,298✔
112
    }
116,298✔
113

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

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

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

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

140
    if let Some((min, max)) = min.zip(max) {
57,691✔
141
        return Ok(Some(MinMaxResult { min, max }));
436✔
142
    }
57,255✔
143

144
    let args = InvocationArgs {
57,255✔
145
        inputs: &[array.into()],
57,255✔
146
        options: &(),
57,255✔
147
    };
57,255✔
148
    for kernel in kernels {
149,427✔
149
        if let Some(output) = kernel.invoke(&args)? {
149,351✔
150
            return MinMaxResult::from_scalar(output.unwrap_scalar()?);
57,179✔
151
        }
92,172✔
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,149✔
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>> {
149,269✔
184
        let inputs = UnaryArgs::<()>::try_from(args)?;
149,269✔
185
        let Some(array) = inputs.array.as_opt::<V>() else {
149,269✔
186
            return Ok(None);
92,127✔
187
        };
188
        let dtype = DType::Struct(
57,142✔
189
            StructFields::new(
57,142✔
190
                ["min", "max"].into(),
57,142✔
191
                vec![array.dtype().clone(), array.dtype().clone()],
57,142✔
192
            ),
57,142✔
193
            Nullability::Nullable,
57,142✔
194
        );
57,142✔
195
        Ok(Some(match V::min_max(&self.0, array)? {
57,142✔
196
            None => Scalar::null(dtype).into(),
5,412✔
197
            Some(MinMaxResult { min, max }) => Scalar::struct_(dtype, vec![min, max]).into(),
51,730✔
198
        }))
199
    }
149,269✔
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