• 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

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,695✔
27
    is_sorted_opts(array, false)
5,695✔
28
}
5,695✔
29

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

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

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

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

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

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

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

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

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

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

94
            is_sorted
3,179✔
95
        };
96

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

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

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

108
    fn is_elementwise(&self) -> bool {
11,122✔
109
        true
11,122✔
110
    }
11,122✔
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> {
40,892✔
122
        if value.inputs.len() != 1 {
40,892✔
UNCOV
123
            vortex_bail!(
×
UNCOV
124
                "IsSorted function requires exactly one argument, got {}",
×
UNCOV
125
                value.inputs.len()
×
126
            );
127
        }
40,892✔
128
        let array = value.inputs[0]
40,892✔
129
            .array()
40,892✔
130
            .ok_or_else(|| vortex_err!("Invalid argument type for is sorted function"))?;
40,892✔
131
        let options = *value
40,892✔
132
            .options
40,892✔
133
            .as_any()
40,892✔
134
            .downcast_ref::<IsSortedOptions>()
40,892✔
135
            .ok_or_else(|| vortex_err!("Invalid options type for is sorted function"))?;
40,892✔
136

137
        Ok(IsSortedArgs {
40,892✔
138
            array,
40,892✔
139
            strict: options.strict,
40,892✔
140
        })
40,892✔
141
    }
40,892✔
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 {
40,892✔
151
        self
40,892✔
152
    }
40,892✔
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>> {
29,770✔
169
        let IsSortedArgs { array, strict } = IsSortedArgs::try_from(args)?;
29,770✔
170
        let Some(array) = array.as_opt::<V>() else {
29,770✔
171
            return Ok(None);
24,960✔
172
        };
173

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

180
        Ok(Some(Scalar::from(is_sorted).into()))
4,810✔
181
    }
29,770✔
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,783✔
202
    where
1,783✔
203
        Self: Sized,
1,783✔
204
        Self::Item: PartialOrd,
1,783✔
205
    {
206
        self.is_sorted_by(|a, b| a < b)
898,488✔
207
    }
1,783✔
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,090✔
218
    array: &dyn Array,
6,090✔
219
    kernels: &[ArcRef<dyn Kernel>],
6,090✔
220
    strict: bool,
6,090✔
221
) -> VortexResult<bool> {
6,090✔
222
    // Arrays with 0 or 1 elements are strict sorted.
223
    if array.len() <= 1 {
6,090✔
224
        return Ok(true);
1,274✔
225
    }
4,816✔
226

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

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

234
    // Enforce strictness before we even try to check if the array is sorted.
235
    if strict {
4,816✔
236
        match invalid_count {
1,789✔
237
            // We can keep going
238
            0 => {}
1,783✔
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,027✔
248

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

254
    for kernel in kernels {
29,772✔
255
        if let Some(output) = kernel.invoke(&args)? {
29,770✔
256
            return Ok(output
4,810✔
257
                .unwrap_scalar()?
4,810✔
258
                .as_bool()
4,810✔
259
                .value()
4,810✔
260
                .vortex_expect("non-nullable"));
4,810✔
261
        }
24,960✔
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,090✔
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