• 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

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

4
use std::any::Any;
5
use std::sync::LazyLock;
6

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

12
use crate::Array;
13
use crate::arrays::{ConstantVTable, NullVTable};
14
use crate::compute::{ComputeFn, ComputeFnVTable, InvocationArgs, Kernel, Options, Output};
15
use crate::stats::{Precision, Stat};
16
use crate::vtable::VTable;
17

18
static IS_SORTED_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
2,259✔
19
    let compute = ComputeFn::new("is_sorted".into(), ArcRef::new_ref(&IsSorted));
2,259✔
20
    for kernel in inventory::iter::<IsSortedKernelRef> {
24,173✔
21
        compute.register_kernel(kernel.0.clone());
21,914✔
22
    }
21,914✔
23
    compute
2,259✔
24
});
2,259✔
25

26
pub fn is_sorted(array: &dyn Array) -> VortexResult<bool> {
5,687✔
27
    is_sorted_opts(array, false)
5,687✔
28
}
5,687✔
29

30
pub fn is_strict_sorted(array: &dyn Array) -> VortexResult<bool> {
5,377✔
31
    is_sorted_opts(array, true)
5,377✔
32
}
5,377✔
33

34
pub fn is_sorted_opts(array: &dyn Array, strict: bool) -> VortexResult<bool> {
11,064✔
35
    Ok(IS_SORTED_FN
11,064✔
36
        .invoke(&InvocationArgs {
11,064✔
37
            inputs: &[array.into()],
11,064✔
38
            options: &IsSortedOptions { strict },
11,064✔
39
        })?
11,064✔
40
        .unwrap_scalar()?
11,064✔
41
        .as_bool()
11,064✔
42
        .value()
11,064✔
43
        .vortex_expect("non-nullable"))
11,064✔
44
}
11,064✔
45

46
struct IsSorted;
47
impl ComputeFnVTable for IsSorted {
48
    fn invoke(
11,064✔
49
        &self,
11,064✔
50
        args: &InvocationArgs,
11,064✔
51
        kernels: &[ArcRef<dyn Kernel>],
11,064✔
52
    ) -> VortexResult<Output> {
11,064✔
53
        let IsSortedArgs { array, strict } = IsSortedArgs::try_from(args)?;
11,064✔
54

55
        // We currently don't support sorting struct arrays.
56
        if array.dtype().is_struct() {
11,064✔
57
            return Ok(Scalar::from(false).into());
5,032✔
58
        }
6,032✔
59

60
        let is_sorted = if strict {
6,032✔
UNCOV
61
            if let Some(Precision::Exact(value)) =
×
62
                array.statistics().get_as::<bool>(Stat::IsStrictSorted)
2,861✔
63
            {
UNCOV
64
                return Ok(Scalar::from(value).into());
×
65
            }
2,861✔
66

67
            let is_strict_sorted = is_sorted_impl(array, kernels, true)?;
2,861✔
68
            let array_stats = array.statistics();
2,861✔
69

70
            if is_strict_sorted {
2,861✔
71
                array_stats.set(Stat::IsSorted, Precision::Exact(true.into()));
2,840✔
72
                array_stats.set(Stat::IsStrictSorted, Precision::Exact(true.into()));
2,840✔
73
            } else {
2,840✔
74
                array_stats.set(Stat::IsStrictSorted, Precision::Exact(false.into()));
21✔
75
            }
21✔
76

77
            is_strict_sorted
2,861✔
78
        } else {
79
            if let Some(Precision::Exact(value)) = array.statistics().get_as::<bool>(Stat::IsSorted)
3,171✔
80
            {
UNCOV
81
                return Ok(Scalar::from(value).into());
×
82
            }
3,171✔
83

84
            let is_sorted = is_sorted_impl(array, kernels, false)?;
3,171✔
85
            let array_stats = array.statistics();
3,171✔
86

87
            if is_sorted {
3,171✔
88
                array_stats.set(Stat::IsSorted, Precision::Exact(true.into()));
915✔
89
            } else {
2,256✔
90
                array_stats.set(Stat::IsSorted, Precision::Exact(false.into()));
2,256✔
91
                array_stats.set(Stat::IsStrictSorted, Precision::Exact(false.into()));
2,256✔
92
            }
2,256✔
93

94
            is_sorted
3,171✔
95
        };
96

97
        Ok(Scalar::from(is_sorted).into())
6,032✔
98
    }
11,064✔
99

100
    fn return_dtype(&self, _args: &InvocationArgs) -> VortexResult<DType> {
11,064✔
101
        Ok(DType::Bool(Nullability::NonNullable))
11,064✔
102
    }
11,064✔
103

104
    fn return_len(&self, _args: &InvocationArgs) -> VortexResult<usize> {
11,064✔
105
        Ok(1)
11,064✔
106
    }
11,064✔
107

108
    fn is_elementwise(&self) -> bool {
11,064✔
109
        true
11,064✔
110
    }
11,064✔
111
}
112

113
struct IsSortedArgs<'a> {
114
    array: &'a dyn Array,
115
    strict: bool,
116
}
117

118
impl<'a> TryFrom<&InvocationArgs<'a>> for IsSortedArgs<'a> {
119
    type Error = VortexError;
120

121
    fn try_from(value: &InvocationArgs<'a>) -> Result<Self, Self::Error> {
28,416✔
122
        if value.inputs.len() != 1 {
28,416✔
UNCOV
123
            vortex_bail!(
×
UNCOV
124
                "IsSorted function requires exactly one argument, got {}",
×
UNCOV
125
                value.inputs.len()
×
126
            );
127
        }
28,416✔
128
        let array = value.inputs[0]
28,416✔
129
            .array()
28,416✔
130
            .ok_or_else(|| vortex_err!("Invalid argument type for is sorted function"))?;
28,416✔
131
        let options = *value
28,416✔
132
            .options
28,416✔
133
            .as_any()
28,416✔
134
            .downcast_ref::<IsSortedOptions>()
28,416✔
135
            .ok_or_else(|| vortex_err!("Invalid options type for is sorted function"))?;
28,416✔
136

137
        Ok(IsSortedArgs {
28,416✔
138
            array,
28,416✔
139
            strict: options.strict,
28,416✔
140
        })
28,416✔
141
    }
28,416✔
142
}
143

144
#[derive(Clone, Copy)]
145
struct IsSortedOptions {
146
    strict: bool,
147
}
148

149
impl Options for IsSortedOptions {
150
    fn as_any(&self) -> &dyn Any {
28,416✔
151
        self
28,416✔
152
    }
28,416✔
153
}
154

155
pub struct IsSortedKernelRef(ArcRef<dyn Kernel>);
156
inventory::collect!(IsSortedKernelRef);
157

158
#[derive(Debug)]
159
pub struct IsSortedKernelAdapter<V: VTable>(pub V);
160

161
impl<V: VTable + IsSortedKernel> IsSortedKernelAdapter<V> {
162
    pub const fn lift(&'static self) -> IsSortedKernelRef {
×
163
        IsSortedKernelRef(ArcRef::new_ref(self))
×
164
    }
×
165
}
166

167
impl<V: VTable + IsSortedKernel> Kernel for IsSortedKernelAdapter<V> {
168
    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
17,352✔
169
        let IsSortedArgs { array, strict } = IsSortedArgs::try_from(args)?;
17,352✔
170
        let Some(array) = array.as_opt::<V>() else {
17,352✔
171
            return Ok(None);
12,560✔
172
        };
173

174
        let is_sorted = if strict {
4,792✔
175
            V::is_strict_sorted(&self.0, array)?
1,773✔
176
        } else {
177
            V::is_sorted(&self.0, array)?
3,019✔
178
        };
179

180
        Ok(Some(Scalar::from(is_sorted).into()))
4,792✔
181
    }
17,352✔
182
}
183

184
pub trait IsSortedKernel: VTable {
185
    /// # Preconditions
186
    /// - The array's length is > 1.
187
    /// - The array is not encoded as `NullArray` or `ConstantArray`.
188
    /// - If doing a `strict` check, if the array is nullable, it'll have at most 1 null element
189
    ///   as the first item in the array.
190
    fn is_sorted(&self, array: &Self::Array) -> VortexResult<bool>;
191

192
    fn is_strict_sorted(&self, array: &Self::Array) -> VortexResult<bool>;
193
}
194

195
#[allow(clippy::wrong_self_convention)]
196
/// Helper methods to check sortedness with strictness
197
pub trait IsSortedIteratorExt: Iterator
198
where
199
    <Self as Iterator>::Item: PartialOrd,
200
{
201
    fn is_strict_sorted(self) -> bool
1,773✔
202
    where
1,773✔
203
        Self: Sized,
1,773✔
204
        Self::Item: PartialOrd,
1,773✔
205
    {
206
        self.is_sorted_by(|a, b| a < b)
794,498✔
207
    }
1,773✔
208
}
209

210
impl<T> IsSortedIteratorExt for T
211
where
212
    T: Iterator + ?Sized,
213
    T::Item: PartialOrd,
214
{
215
}
216

217
fn is_sorted_impl(
6,032✔
218
    array: &dyn Array,
6,032✔
219
    kernels: &[ArcRef<dyn Kernel>],
6,032✔
220
    strict: bool,
6,032✔
221
) -> VortexResult<bool> {
6,032✔
222
    // Arrays with 0 or 1 elements are strict sorted.
223
    if array.len() <= 1 {
6,032✔
224
        return Ok(true);
1,234✔
225
    }
4,798✔
226

227
    // Constant and null arrays are always sorted, but not strict sorted.
228
    if array.is::<ConstantVTable>() || array.is::<NullVTable>() {
4,798✔
229
        return Ok(!strict);
×
230
    }
4,798✔
231

232
    let invalid_count = array.invalid_count()?;
4,798✔
233

234
    // Enforce strictness before we even try to check if the array is sorted.
235
    if strict {
4,798✔
236
        match invalid_count {
1,779✔
237
            // We can keep going
238
            0 => {}
1,773✔
239
            // If we have a potential null value - it has to be the first one.
240
            1 => {
241
                if !array.is_invalid(0)? {
3✔
242
                    return Ok(false);
1✔
243
                }
2✔
244
            }
245
            _ => return Ok(false),
3✔
246
        }
247
    }
3,019✔
248

249
    let args = InvocationArgs {
4,794✔
250
        inputs: &[array.into()],
4,794✔
251
        options: &IsSortedOptions { strict },
4,794✔
252
    };
4,794✔
253

254
    for kernel in kernels {
17,354✔
255
        if let Some(output) = kernel.invoke(&args)? {
17,352✔
256
            return Ok(output
4,792✔
257
                .unwrap_scalar()?
4,792✔
258
                .as_bool()
4,792✔
259
                .value()
4,792✔
260
                .vortex_expect("non-nullable"));
4,792✔
261
        }
12,560✔
262
    }
263
    if let Some(output) = array.invoke(&IS_SORTED_FN, &args)? {
2✔
264
        return Ok(output
×
265
            .unwrap_scalar()?
×
266
            .as_bool()
×
267
            .value()
×
268
            .vortex_expect("non-nullable"));
×
269
    }
2✔
270

271
    if !array.is_canonical() {
2✔
272
        log::debug!(
2✔
273
            "No is_sorted implementation found for {}",
×
274
            array.encoding_id()
×
275
        );
276

277
        // Recurse to canonical implementation
278
        let array = array.to_canonical()?;
2✔
279

280
        return if strict {
2✔
281
            is_strict_sorted(array.as_ref())
2✔
282
        } else {
283
            is_sorted(array.as_ref())
×
284
        };
285
    }
×
286

287
    vortex_bail!(
×
288
        "No is_sorted function for canonical array: {}",
×
289
        array.encoding_id(),
×
290
    )
291
}
6,032✔
292

293
#[cfg(test)]
294
mod tests {
295
    use vortex_buffer::buffer;
296

297
    use crate::IntoArray;
298
    use crate::arrays::{BoolArray, PrimitiveArray};
299
    use crate::compute::{is_sorted, is_strict_sorted};
300
    use crate::validity::Validity;
301
    #[test]
302
    fn test_is_sorted() {
1✔
303
        assert!(
1✔
304
            is_sorted(PrimitiveArray::new(buffer!(0, 1, 2, 3), Validity::AllValid).as_ref())
1✔
305
                .unwrap()
1✔
306
        );
307
        assert!(
1✔
308
            is_sorted(
1✔
309
                PrimitiveArray::new(
1✔
310
                    buffer!(0, 1, 2, 3),
1✔
311
                    Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array())
1✔
312
                )
1✔
313
                .as_ref()
1✔
314
            )
315
            .unwrap()
1✔
316
        );
317
        assert!(
1✔
318
            !is_sorted(
1✔
319
                PrimitiveArray::new(
1✔
320
                    buffer!(0, 1, 2, 3),
1✔
321
                    Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array())
1✔
322
                )
1✔
323
                .as_ref()
1✔
324
            )
1✔
325
            .unwrap()
1✔
326
        );
327

328
        assert!(
1✔
329
            !is_sorted(PrimitiveArray::new(buffer!(0, 1, 3, 2), Validity::AllValid).as_ref())
1✔
330
                .unwrap()
1✔
331
        );
332
        assert!(
1✔
333
            !is_sorted(
1✔
334
                PrimitiveArray::new(
1✔
335
                    buffer!(0, 1, 3, 2),
1✔
336
                    Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array()),
1✔
337
                )
1✔
338
                .as_ref()
1✔
339
            )
1✔
340
            .unwrap(),
1✔
341
        );
342
    }
1✔
343

344
    #[test]
345
    fn test_is_strict_sorted() {
1✔
346
        assert!(
1✔
347
            is_strict_sorted(PrimitiveArray::new(buffer!(0, 1, 2, 3), Validity::AllValid).as_ref())
1✔
348
                .unwrap()
1✔
349
        );
350
        assert!(
1✔
351
            is_strict_sorted(
1✔
352
                PrimitiveArray::new(
1✔
353
                    buffer!(0, 1, 2, 3),
1✔
354
                    Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array())
1✔
355
                )
1✔
356
                .as_ref()
1✔
357
            )
358
            .unwrap()
1✔
359
        );
360
        assert!(
1✔
361
            !is_strict_sorted(
1✔
362
                PrimitiveArray::new(
1✔
363
                    buffer!(0, 1, 2, 3),
1✔
364
                    Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
1✔
365
                )
1✔
366
                .as_ref()
1✔
367
            )
1✔
368
            .unwrap(),
1✔
369
        );
370

371
        assert!(
1✔
372
            !is_strict_sorted(
1✔
373
                PrimitiveArray::new(
1✔
374
                    buffer!(0, 1, 3, 2),
1✔
375
                    Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array()),
1✔
376
                )
1✔
377
                .as_ref()
1✔
378
            )
1✔
379
            .unwrap(),
1✔
380
        );
381
    }
1✔
382
}
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