Skip to main content

vortex_sequence/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7use std::hash::Hasher;
8
9use num_traits::AsPrimitive;
10use prost::Message;
11use smallvec::smallvec;
12use vortex_array::Array;
13use vortex_array::ArrayEq;
14use vortex_array::ArrayHash;
15use vortex_array::ArrayId;
16use vortex_array::ArrayParts;
17use vortex_array::ArrayRef;
18use vortex_array::ArrayView;
19use vortex_array::EqMode;
20use vortex_array::ExecutionCtx;
21use vortex_array::ExecutionResult;
22use vortex_array::buffer::BufferHandle;
23use vortex_array::dtype::DType;
24use vortex_array::dtype::NativePType;
25use vortex_array::dtype::Nullability;
26use vortex_array::dtype::Nullability::NonNullable;
27use vortex_array::dtype::PType;
28use vortex_array::expr::stats::Precision as StatPrecision;
29use vortex_array::expr::stats::Stat;
30use vortex_array::match_each_integer_ptype;
31use vortex_array::match_each_pvalue;
32use vortex_array::scalar::PValue;
33use vortex_array::scalar::Scalar;
34use vortex_array::scalar::ScalarValue;
35use vortex_array::serde::ArrayChildren;
36use vortex_array::stats::StatsSet;
37use vortex_array::validity::Validity;
38use vortex_array::vtable::OperationsVTable;
39use vortex_array::vtable::VTable;
40use vortex_array::vtable::ValidityVTable;
41use vortex_error::VortexExpect;
42use vortex_error::VortexResult;
43use vortex_error::vortex_bail;
44use vortex_error::vortex_ensure;
45use vortex_error::vortex_err;
46use vortex_error::vortex_panic;
47use vortex_session::VortexSession;
48use vortex_session::registry::CachedId;
49
50use crate::compress::sequence_decompress;
51use crate::eval;
52use crate::eval::SequenceValue;
53use crate::rules::RULES;
54
55/// A [`Sequence`]-encoded Vortex array.
56pub type SequenceArray = Array<Sequence>;
57
58#[derive(Clone, prost::Message)]
59pub struct SequenceMetadata {
60    #[prost(message, tag = "1")]
61    base: Option<vortex_proto::scalar::ScalarValue>,
62    #[prost(message, tag = "2")]
63    multiplier: Option<vortex_proto::scalar::ScalarValue>,
64}
65
66pub(super) const SLOT_NAMES: [&str; 0] = [];
67
68/// An array representing the equation `A[i] = base + i * multiplier`.
69///
70/// The base uses the output ptype, while the step is normalized to `i64` or `u64` and may fall
71/// outside that ptype. Construction ensures every sequence value fits the output ptype.
72#[derive(Clone, Debug)]
73pub struct SequenceData {
74    base: PValue,
75    multiplier: PValue,
76}
77
78impl Display for SequenceData {
79    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80        write!(f, "base: {}, multiplier: {}", self.base, self.multiplier)
81    }
82}
83
84pub struct SequenceDataParts {
85    pub base: PValue,
86    pub multiplier: PValue,
87    pub ptype: PType,
88}
89
90impl SequenceData {
91    pub(crate) fn try_new_typed<T: NativePType + Into<PValue>>(
92        base: T,
93        multiplier: T,
94        nullability: Nullability,
95        length: usize,
96    ) -> VortexResult<Self> {
97        Self::try_new(
98            base.into(),
99            multiplier.into(),
100            T::PTYPE,
101            nullability,
102            length,
103        )
104    }
105
106    /// Constructs a sequence array using two integer values, validated against output `ptype`.
107    pub(crate) fn try_new(
108        base: PValue,
109        multiplier: PValue,
110        ptype: PType,
111        nullability: Nullability,
112        length: usize,
113    ) -> VortexResult<Self> {
114        let dtype = DType::Primitive(ptype, nullability);
115        Self::validate(base, multiplier, &dtype, length)?;
116        let (base, multiplier) = Self::normalize(base, multiplier, ptype)?;
117
118        Ok(unsafe { Self::new_unchecked(base, multiplier) })
119    }
120
121    pub fn validate(
122        base: PValue,
123        multiplier: PValue,
124        dtype: &DType,
125        length: usize,
126    ) -> VortexResult<()> {
127        let DType::Primitive(ptype, _) = dtype else {
128            vortex_bail!("only primitive dtypes are supported in SequenceArray currently");
129        };
130
131        if !ptype.is_int() {
132            vortex_bail!("only integer ptypes are supported in SequenceArray currently")
133        }
134
135        vortex_ensure!(length > 0, "SequenceArray length must be greater than zero");
136
137        Self::narrowed_base(base, *ptype)?;
138        Self::ensure_last_expressible(base, multiplier, *ptype, length)
139    }
140
141    /// Ensures the final value fits `ptype` without overflowing intermediate arithmetic.
142    fn ensure_last_expressible(
143        base: PValue,
144        multiplier: PValue,
145        ptype: PType,
146        length: usize,
147    ) -> VortexResult<()> {
148        let steps = (length - 1) as u64;
149        let (ascending, magnitude) = eval::step_parts(multiplier)
150            .ok_or_else(|| vortex_err!("step {multiplier} must be an integer"))?;
151        if steps == 0 || magnitude == 0 {
152            return Ok(());
153        }
154
155        // Measure room in the ptype's signedness so large `u64` bases remain exact.
156        let room = if ptype.is_signed_int() {
157            let base = base.cast::<i64>()?;
158            let max = i64::try_from(ptype.max_value_as_u64())
159                .vortex_expect("a signed ptype's max fits i64");
160            base.abs_diff(if ascending { max } else { -max - 1 })
161        } else {
162            let base = base.cast::<u64>()?;
163            if ascending {
164                ptype.max_value_as_u64() - base
165            } else {
166                base
167            }
168        };
169
170        vortex_ensure!(
171            steps <= room / magnitude,
172            "final value not expressible, base = {base:?}, multiplier = {multiplier:?}, len = {length}"
173        );
174        Ok(())
175    }
176
177    /// The step's ptype: the serialized form preserves its signedness but not its width.
178    fn multiplier_ptype_from_proto(
179        multiplier: &vortex_proto::scalar::ScalarValue,
180    ) -> VortexResult<PType> {
181        use vortex_proto::scalar::scalar_value::Kind;
182        match multiplier
183            .kind
184            .as_ref()
185            .ok_or_else(|| vortex_err!("multiplier value missing kind"))?
186        {
187            Kind::Int64Value(_) => Ok(PType::I64),
188            Kind::Uint64Value(_) => Ok(PType::U64),
189            _ => vortex_bail!("only integer ptypes are supported in SequenceArray currently"),
190        }
191    }
192
193    fn narrowed_base(base: PValue, ptype: PType) -> VortexResult<PValue> {
194        vortex_ensure!(base.ptype().is_int(), "base {base} must be an integer");
195        match_each_integer_ptype!(ptype, |P| { Ok(PValue::from(base.cast::<P>()?)) })
196    }
197
198    /// Puts `base` into the output ptype and the step into its canonical ptype.
199    fn normalize(base: PValue, multiplier: PValue, ptype: PType) -> VortexResult<(PValue, PValue)> {
200        let base = Self::narrowed_base(base, ptype)?;
201
202        // Give equivalent steps the same representation.
203        let multiplier = match_each_pvalue!(
204            multiplier,
205            uint: |v| {
206                let v: u64 = v.as_();
207                i64::try_from(v).map(PValue::from).unwrap_or(PValue::U64(v))
208            },
209            int: |v| {
210                let v: i64 = v.as_();
211                PValue::from(v)
212            },
213            float: |v| { vortex_bail!("step {v} must be an integer") }
214        );
215
216        Ok((base, multiplier))
217    }
218
219    /// Constructs a [`SequenceArray`] payload without validation.
220    ///
221    /// # Safety
222    ///
223    /// The caller must ensure that:
224    /// - `base` uses the outer dtype's integer ptype.
225    /// - `multiplier` is a canonical `i64` or `u64`.
226    /// - every sequence value fits the outer dtype's ptype.
227    pub(crate) unsafe fn new_unchecked(base: PValue, multiplier: PValue) -> Self {
228        Self { base, multiplier }
229    }
230
231    /// The array's output ptype.
232    pub fn ptype(&self) -> PType {
233        self.base.ptype()
234    }
235
236    pub fn base(&self) -> PValue {
237        self.base
238    }
239
240    pub fn multiplier(&self) -> PValue {
241        self.multiplier
242    }
243
244    /// `base` and `multiplier` reduced into `O`, the type the values are computed in.
245    pub(crate) fn wrapping_parts<O: SequenceValue>(&self) -> VortexResult<(O, O)> {
246        eval::wrapping_parts(self.base, self.multiplier).ok_or_else(|| {
247            vortex_err!(
248                "SequenceArray values must be integers, got base {:?} and step {:?}",
249                self.base,
250                self.multiplier
251            )
252        })
253    }
254
255    /// The two's-complement bits of `base` and `multiplier`, widened to 64 bits.
256    pub fn wrapping_bits(&self) -> VortexResult<(u64, u64)> {
257        self.wrapping_parts::<u64>()
258    }
259
260    pub fn into_parts(self) -> SequenceDataParts {
261        SequenceDataParts {
262            base: self.base,
263            multiplier: self.multiplier,
264            ptype: self.ptype(),
265        }
266    }
267
268    pub(crate) fn index_value(&self, idx: usize) -> PValue {
269        match_each_integer_ptype!(self.ptype(), |O| {
270            let (base, multiplier) = self
271                .wrapping_parts::<O>()
272                .vortex_expect("sequence values are integers");
273            PValue::from(eval::wrapping_value(base, multiplier, idx))
274        })
275    }
276}
277
278// Normalization gives equal sequences the same value tags. Compare tags first because `PValue`
279// equality can panic for mixed signedness outside the `i64` range.
280impl ArrayHash for SequenceData {
281    fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
282        self.base.hash(state);
283        self.multiplier.hash(state);
284    }
285}
286
287impl ArrayEq for SequenceData {
288    fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
289        self.base.ptype() == other.base.ptype()
290            && self.multiplier.ptype() == other.multiplier.ptype()
291            && self.base == other.base
292            && self.multiplier == other.multiplier
293    }
294}
295
296impl VTable for Sequence {
297    type TypedArrayData = SequenceData;
298
299    type OperationsVTable = Self;
300    type ValidityVTable = Self;
301
302    fn id(&self) -> ArrayId {
303        static ID: CachedId = CachedId::new("vortex.sequence");
304        *ID
305    }
306
307    fn validate(
308        &self,
309        data: &Self::TypedArrayData,
310        dtype: &DType,
311        len: usize,
312        _slots: &[Option<ArrayRef>],
313    ) -> VortexResult<()> {
314        SequenceData::validate(data.base, data.multiplier, dtype, len)
315    }
316
317    fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
318        0
319    }
320
321    fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
322        vortex_panic!("SequenceArray buffer index {idx} out of bounds")
323    }
324
325    fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
326        vortex_panic!("SequenceArray buffer_name index {idx} out of bounds")
327    }
328
329    fn with_buffers(
330        &self,
331        array: ArrayView<'_, Self>,
332        buffers: &[BufferHandle],
333    ) -> VortexResult<ArrayParts<Self>> {
334        vortex_array::vtable::with_empty_buffers(self, array, buffers)
335    }
336
337    fn serialize(
338        array: ArrayView<'_, Self>,
339        _session: &VortexSession,
340    ) -> VortexResult<Option<Vec<u8>>> {
341        let metadata = SequenceMetadata {
342            base: Some((&array.base()).into()),
343            multiplier: Some((&array.multiplier()).into()),
344        };
345
346        Ok(Some(metadata.encode_to_vec()))
347    }
348
349    fn deserialize(
350        &self,
351        dtype: &DType,
352        len: usize,
353        metadata: &[u8],
354        buffers: &[BufferHandle],
355        children: &dyn ArrayChildren,
356        session: &VortexSession,
357    ) -> VortexResult<ArrayParts<Self>> {
358        vortex_ensure!(
359            buffers.is_empty(),
360            "SequenceArray expects 0 buffers, got {}",
361            buffers.len()
362        );
363        vortex_ensure!(
364            children.is_empty(),
365            "SequenceArray expects 0 children, got {}",
366            children.len()
367        );
368        let DType::Primitive(output_ptype, _) = dtype else {
369            vortex_bail!(
370                "only primitive dtypes are supported in SequenceArray currently, got {dtype}"
371            );
372        };
373        let metadata = SequenceMetadata::decode(metadata)?;
374
375        let base_metadata = metadata
376            .base
377            .as_ref()
378            .ok_or_else(|| vortex_err!("base required"))?;
379
380        let multiplier_metadata = metadata
381            .multiplier
382            .as_ref()
383            .ok_or_else(|| vortex_err!("multiplier required"))?;
384
385        // We go via Scalar to validate that the value is valid for the ptype.
386        let base = Scalar::from_proto_value(
387            base_metadata,
388            &DType::Primitive(*output_ptype, NonNullable),
389            session,
390        )?
391        .as_primitive()
392        .pvalue()
393        .vortex_expect("sequence array base should be a non-nullable primitive");
394
395        // The serialized step preserves signedness independently of the output ptype.
396        let multiplier_ptype = SequenceData::multiplier_ptype_from_proto(multiplier_metadata)?;
397        let multiplier = Scalar::from_proto_value(
398            multiplier_metadata,
399            &DType::Primitive(multiplier_ptype, NonNullable),
400            session,
401        )?
402        .as_primitive()
403        .pvalue()
404        .vortex_expect("sequence array multiplier should be a non-nullable primitive");
405
406        let data =
407            SequenceData::try_new(base, multiplier, *output_ptype, dtype.nullability(), len)?;
408        Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data))
409    }
410
411    fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
412        SLOT_NAMES[idx].to_string()
413    }
414
415    fn execute(array: Array<Self>, _ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
416        sequence_decompress(&array).map(ExecutionResult::done)
417    }
418
419    fn reduce_parent(
420        array: ArrayView<'_, Self>,
421        parent: &ArrayRef,
422        child_idx: usize,
423    ) -> VortexResult<Option<ArrayRef>> {
424        RULES.evaluate(array, parent, child_idx)
425    }
426}
427
428impl OperationsVTable<Sequence> for Sequence {
429    fn scalar_at(
430        array: ArrayView<'_, Sequence>,
431        index: usize,
432        _ctx: &mut ExecutionCtx,
433    ) -> VortexResult<Scalar> {
434        Scalar::try_new(
435            array.dtype().clone(),
436            Some(ScalarValue::Primitive(array.index_value(index))),
437        )
438    }
439}
440
441impl ValidityVTable<Sequence> for Sequence {
442    fn validity(_array: ArrayView<'_, Sequence>) -> VortexResult<Validity> {
443        Ok(Validity::AllValid)
444    }
445}
446
447#[derive(Clone, Debug)]
448pub struct Sequence;
449
450impl Sequence {
451    fn stats(multiplier: PValue) -> StatsSet {
452        // A sequence A[i] = base + i * multiplier is sorted iff multiplier >= 0,
453        // and strictly sorted iff multiplier > 0.
454        let (is_sorted, is_strict_sorted) = match_each_pvalue!(
455            multiplier,
456            uint: |v| { (true, v > 0) },
457            int: |v| { (v >= 0, v > 0) },
458            float: |_v| { unreachable!("float multiplier not supported") }
459        );
460
461        // SAFETY: we don't have duplicate stats.
462        unsafe {
463            StatsSet::new_unchecked(smallvec![
464                (Stat::IsSorted, StatPrecision::Exact(is_sorted.into())),
465                (
466                    Stat::IsStrictSorted,
467                    StatPrecision::Exact(is_strict_sorted.into()),
468                ),
469            ])
470        }
471    }
472
473    /// Construct a new [`SequenceArray`] from pre-validated parts.
474    ///
475    /// Arguments are normalized before constructing the array.
476    ///
477    /// # Safety
478    ///
479    /// Caller must ensure the sequence is logically compatible with the provided dtype and len.
480    pub(crate) unsafe fn new_unchecked(
481        base: PValue,
482        multiplier: PValue,
483        ptype: PType,
484        nullability: Nullability,
485        length: usize,
486    ) -> SequenceArray {
487        let dtype = DType::Primitive(ptype, nullability);
488        let (base, multiplier) = SequenceData::normalize(base, multiplier, ptype)
489            .vortex_expect("SequenceArray parts must be representable in the output ptype");
490        let stats = Self::stats(multiplier);
491        let data = unsafe { SequenceData::new_unchecked(base, multiplier) };
492        unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
493            .with_stats_set(stats)
494    }
495
496    /// Construct a new [`SequenceArray`] from its components.
497    pub fn try_new(
498        base: PValue,
499        multiplier: PValue,
500        ptype: PType,
501        nullability: Nullability,
502        length: usize,
503    ) -> VortexResult<SequenceArray> {
504        let dtype = DType::Primitive(ptype, nullability);
505        let data = SequenceData::try_new(base, multiplier, ptype, nullability, length)?;
506        let stats = Self::stats(data.multiplier());
507        Ok(
508            unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
509                .with_stats_set(stats),
510        )
511    }
512
513    /// Construct a new typed [`SequenceArray`] from base/multiplier values.
514    pub fn try_new_typed<T: NativePType + Into<PValue>>(
515        base: T,
516        multiplier: T,
517        nullability: Nullability,
518        length: usize,
519    ) -> VortexResult<SequenceArray> {
520        let ptype = T::PTYPE;
521        let dtype = DType::Primitive(ptype, nullability);
522        let data = SequenceData::try_new_typed(base, multiplier, nullability, length)?;
523        let stats = Self::stats(data.multiplier());
524        Ok(
525            unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
526                .with_stats_set(stats),
527        )
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use std::sync::LazyLock;
534
535    use rstest::rstest;
536    use vortex_array::ArrayContext;
537    use vortex_array::ArrayEq;
538    use vortex_array::EqMode;
539    use vortex_array::IntoArray;
540    use vortex_array::VortexSessionExecute;
541    use vortex_array::arrays::PrimitiveArray;
542    use vortex_array::assert_arrays_eq;
543    use vortex_array::dtype::DType;
544    use vortex_array::dtype::Nullability;
545    use vortex_array::dtype::PType;
546    use vortex_array::expr::stats::Precision as StatPrecision;
547    use vortex_array::expr::stats::Stat;
548    use vortex_array::expr::stats::StatsProviderExt;
549    use vortex_array::scalar::PValue;
550    use vortex_array::scalar::Scalar;
551    use vortex_array::scalar::ScalarValue;
552    use vortex_array::serde::SerializeOptions;
553    use vortex_array::serde::SerializedArray;
554    use vortex_buffer::ByteBufferMut;
555    use vortex_error::VortexResult;
556    use vortex_error::vortex_err;
557    use vortex_session::VortexSession;
558    use vortex_session::registry::ReadContext;
559
560    use crate::Sequence;
561
562    static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
563        let session = vortex_array::array_session();
564        crate::initialize(&session);
565        session
566    });
567
568    #[test]
569    fn test_sequence_canonical() {
570        let arr = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4).unwrap();
571
572        let canon = PrimitiveArray::from_iter((0..4).map(|i| 2i64 + i * 3));
573
574        assert_arrays_eq!(arr, canon, &mut SESSION.create_execution_ctx());
575    }
576
577    #[test]
578    fn test_sequence_slice_canonical() {
579        let arr = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4)
580            .unwrap()
581            .slice(2..3)
582            .unwrap();
583
584        let canon = PrimitiveArray::from_iter((2..3).map(|i| 2i64 + i * 3));
585
586        assert_arrays_eq!(arr, canon, &mut SESSION.create_execution_ctx());
587    }
588
589    #[test]
590    fn test_sequence_scalar_at() {
591        let scalar = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4)
592            .unwrap()
593            .execute_scalar(2, &mut SESSION.create_execution_ctx())
594            .unwrap();
595
596        assert_eq!(
597            scalar,
598            Scalar::try_new(scalar.dtype().clone(), Some(ScalarValue::from(8i64))).unwrap()
599        )
600    }
601
602    #[test]
603    fn test_sequence_min_max() {
604        assert!(Sequence::try_new_typed(-127i8, -1i8, Nullability::NonNullable, 2).is_ok());
605        assert!(Sequence::try_new_typed(126i8, -1i8, Nullability::NonNullable, 2).is_ok());
606    }
607
608    #[test]
609    fn test_sequence_too_big() {
610        assert!(Sequence::try_new_typed(127i8, 1i8, Nullability::NonNullable, 2).is_err());
611        assert!(Sequence::try_new_typed(-128i8, -1i8, Nullability::NonNullable, 2).is_err());
612    }
613
614    #[test]
615    fn positive_multiplier_is_strict_sorted() -> VortexResult<()> {
616        let arr = Sequence::try_new_typed(0i64, 3, Nullability::NonNullable, 4)?;
617
618        let is_sorted = arr
619            .statistics()
620            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
621        assert_eq!(is_sorted, StatPrecision::Exact(true));
622
623        let is_strict_sorted = arr
624            .statistics()
625            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
626        assert_eq!(is_strict_sorted, StatPrecision::Exact(true));
627        Ok(())
628    }
629
630    #[test]
631    fn zero_multiplier_is_sorted_not_strict() -> VortexResult<()> {
632        let arr = Sequence::try_new_typed(5i64, 0, Nullability::NonNullable, 4)?;
633
634        let is_sorted = arr
635            .statistics()
636            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
637        assert_eq!(is_sorted, StatPrecision::Exact(true));
638
639        let is_strict_sorted = arr
640            .statistics()
641            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
642        assert_eq!(is_strict_sorted, StatPrecision::Exact(false));
643        Ok(())
644    }
645
646    #[test]
647    fn negative_multiplier_not_sorted() -> VortexResult<()> {
648        let arr = Sequence::try_new_typed(10i64, -1, Nullability::NonNullable, 4)?;
649
650        let is_sorted = arr
651            .statistics()
652            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
653        assert_eq!(is_sorted, StatPrecision::Exact(false));
654
655        let is_strict_sorted = arr
656            .statistics()
657            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
658        assert_eq!(is_strict_sorted, StatPrecision::Exact(false));
659        Ok(())
660    }
661
662    // This is regression test for an issue caught by the fuzzer, where SequenceArrays with
663    // multiplier > i64::MAX were unable to be constructed.
664    #[test]
665    fn test_large_multiplier_sorted() -> VortexResult<()> {
666        let large_multiplier = (i64::MAX as u64) + 1;
667        let arr = Sequence::try_new_typed(0, large_multiplier, Nullability::NonNullable, 2)?;
668
669        let is_sorted = arr
670            .statistics()
671            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
672
673        let is_strict_sorted = arr
674            .statistics()
675            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
676
677        assert_eq!(is_sorted, StatPrecision::Exact(true));
678        assert_eq!(is_strict_sorted, StatPrecision::Exact(true));
679
680        Ok(())
681    }
682
683    #[rstest]
684    #[case::descending_step_unsigned_output(PValue::from(100i32), PValue::from(-10i32), PType::U8)]
685    #[case::narrow_unsigned(PValue::from(1000u32), PValue::from(100u32), PType::U16)]
686    #[case::signed_output(PValue::from(0i16), PValue::from(1i16), PType::I32)]
687    #[case::signed_step_past_i64_max(PValue::from(0i64), PValue::from(1i64 << 62), PType::U64)]
688    #[case::unsigned_step_past_i64_max(PValue::from(0u64), PValue::from(u64::MAX / 4), PType::U64)]
689    fn serde_roundtrip_preserves_values(
690        #[case] base: PValue,
691        #[case] multiplier: PValue,
692        #[case] output_ptype: PType,
693    ) -> VortexResult<()> {
694        let array = Sequence::try_new(base, multiplier, output_ptype, Nullability::NonNullable, 4)?;
695        assert_eq!(array.ptype(), output_ptype);
696
697        let dtype = array.dtype().clone();
698        let len = array.len();
699        let ctx = ArrayContext::empty();
700        let serialized =
701            array
702                .clone()
703                .into_array()
704                .serialize(&ctx, &SESSION, &SerializeOptions::default())?;
705
706        let mut concat = ByteBufferMut::empty();
707        for buf in serialized {
708            concat.extend_from_slice(buf.as_ref());
709        }
710
711        let decoded = SerializedArray::try_from(concat.freeze())?.decode(
712            &dtype,
713            len,
714            &ReadContext::new(ctx.to_ids()),
715            &SESSION,
716        )?;
717
718        let decoded_sequence = decoded
719            .as_opt::<Sequence>()
720            .ok_or_else(|| vortex_err!("decoded array should still be a SequenceArray"))?;
721        assert_eq!(decoded_sequence.ptype(), output_ptype);
722        assert_eq!(decoded_sequence.multiplier(), array.multiplier());
723        assert_eq!(decoded.dtype(), &dtype);
724        assert_arrays_eq!(decoded, array, &mut SESSION.create_execution_ctx());
725
726        Ok(())
727    }
728
729    #[test]
730    fn descending_step_unsigned_output() -> VortexResult<()> {
731        let mut ctx = SESSION.create_execution_ctx();
732        let array = Sequence::try_new(
733            PValue::from(100i32),
734            PValue::from(-10i32),
735            PType::U8,
736            Nullability::NonNullable,
737            5,
738        )?;
739
740        assert_arrays_eq!(
741            array,
742            PrimitiveArray::from_iter([100u8, 90, 80, 70, 60]),
743            &mut ctx
744        );
745        assert_eq!(
746            array.clone().into_array().execute_scalar(1, &mut ctx)?,
747            Scalar::from(90u8)
748        );
749        assert_arrays_eq!(
750            array.slice(3..5)?,
751            PrimitiveArray::from_iter([70u8, 60]),
752            &mut ctx
753        );
754
755        Ok(())
756    }
757
758    #[test]
759    fn step_spanning_output_range() -> VortexResult<()> {
760        let array = Sequence::try_new(
761            PValue::from(255u8),
762            PValue::from(-255i32),
763            PType::U8,
764            Nullability::NonNullable,
765            2,
766        )?;
767
768        assert_arrays_eq!(
769            array,
770            PrimitiveArray::from_iter([255u8, 0]),
771            &mut SESSION.create_execution_ctx()
772        );
773
774        Ok(())
775    }
776
777    #[test]
778    fn values_past_i64_max() -> VortexResult<()> {
779        let mut ctx = SESSION.create_execution_ctx();
780        let step = 1u64 << 62;
781        let array = Sequence::try_new(
782            PValue::from(0i64),
783            PValue::from(1i64 << 62),
784            PType::U64,
785            Nullability::NonNullable,
786            4,
787        )?;
788
789        assert_arrays_eq!(
790            array,
791            PrimitiveArray::from_iter([0, step, 2 * step, 3 * step]),
792            &mut ctx
793        );
794        assert_eq!(
795            array.into_array().execute_scalar(3, &mut ctx)?,
796            Scalar::from(3 * step)
797        );
798
799        Ok(())
800    }
801
802    #[test]
803    fn eq_across_step_signedness() -> VortexResult<()> {
804        let base = PValue::from((1u64 << 63) - 1);
805        let descending = Sequence::try_new(
806            base,
807            PValue::from(-1i64),
808            PType::U64,
809            Nullability::NonNullable,
810            2,
811        )?
812        .into_array();
813        let ascending = Sequence::try_new(
814            base,
815            PValue::from(1u64 << 63),
816            PType::U64,
817            Nullability::NonNullable,
818            2,
819        )?
820        .into_array();
821
822        assert!(descending.array_eq(&descending.clone(), EqMode::Value));
823        assert!(!descending.array_eq(&ascending, EqMode::Value));
824
825        Ok(())
826    }
827
828    #[test]
829    fn constant_sequence_longer_than_output_range() -> VortexResult<()> {
830        let array = Sequence::try_new(
831            PValue::from(7u8),
832            PValue::from(0i32),
833            PType::U8,
834            Nullability::NonNullable,
835            300,
836        )?;
837
838        assert_arrays_eq!(
839            array,
840            PrimitiveArray::from_iter([7u8; 300]),
841            &mut SESSION.create_execution_ctx()
842        );
843
844        Ok(())
845    }
846
847    #[test]
848    fn deserialize_rejects_values_outside_output_ptype() -> VortexResult<()> {
849        let array = Sequence::try_new_typed(-5i32, 1i32, Nullability::NonNullable, 5)?;
850        let len = array.len();
851        let ctx = ArrayContext::empty();
852        let serialized =
853            array
854                .into_array()
855                .serialize(&ctx, &SESSION, &SerializeOptions::default())?;
856
857        let mut concat = ByteBufferMut::empty();
858        for buf in serialized {
859            concat.extend_from_slice(buf.as_ref());
860        }
861
862        let decoded = SerializedArray::try_from(concat.freeze())?.decode(
863            &DType::Primitive(PType::U8, Nullability::NonNullable),
864            len,
865            &ReadContext::new(ctx.to_ids()),
866            &SESSION,
867        );
868        assert!(decoded.is_err());
869
870        Ok(())
871    }
872}