Skip to main content

vortex_array/arrays/
arbitrary.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::iter;
5use std::ops::RangeInclusive;
6use std::sync::Arc;
7
8use arbitrary::Arbitrary;
9use arbitrary::Error::IncorrectFormat;
10use arbitrary::Result;
11use arbitrary::Unstructured;
12use vortex_buffer::BitBuffer;
13use vortex_buffer::Buffer;
14use vortex_error::VortexExpect;
15
16use crate::ArrayRef;
17use crate::IntoArray;
18use crate::arrays::BoolArray;
19use crate::arrays::ChunkedArray;
20use crate::arrays::NullArray;
21use crate::arrays::Primitive;
22use crate::arrays::PrimitiveArray;
23use crate::arrays::StructArray;
24use crate::arrays::VarBinArray;
25use crate::arrays::VarBinViewArray;
26use crate::arrays::primitive::PrimitiveArrayExt;
27use crate::builders::ArrayBuilder;
28use crate::builders::DecimalBuilder;
29use crate::builders::FixedSizeListBuilder;
30use crate::builders::ListViewBuilder;
31use crate::builders::MapBuilder;
32use crate::dtype::DType;
33use crate::dtype::IntegerPType;
34use crate::dtype::MapDType;
35use crate::dtype::NativePType;
36use crate::dtype::Nullability;
37use crate::dtype::OffsetBuilderPType;
38use crate::dtype::PType;
39use crate::match_each_decimal_value_type;
40use crate::scalar::Scalar;
41use crate::scalar::arbitrary::random_scalar;
42use crate::validity::Validity;
43
44/// A wrapper type to implement `Arbitrary` for `ArrayRef`.
45#[derive(Clone, Debug)]
46pub struct ArbitraryArray(pub ArrayRef);
47
48/// Trait for generating arbitrary values with a caller-provided configuration.
49pub trait ArbitraryWith<'a, C>: Sized {
50    /// Generate an arbitrary value using the provided configuration.
51    fn arbitrary_with_config(u: &mut Unstructured<'a>, config: &C) -> Result<Self>;
52}
53
54/// Configuration for arbitrary array generation.
55#[derive(Clone, Debug)]
56pub struct ArbitraryArrayConfig {
57    /// Fixed dtype, or `None` to generate one from [`Unstructured`].
58    pub dtype: Option<DType>,
59    /// Inclusive range for the total array length.
60    pub len: RangeInclusive<usize>,
61}
62
63impl<'a> ArbitraryWith<'a, ArbitraryArrayConfig> for ArbitraryArray {
64    fn arbitrary_with_config(
65        u: &mut Unstructured<'a>,
66        config: &ArbitraryArrayConfig,
67    ) -> Result<Self> {
68        if config.len.is_empty() {
69            return Err(IncorrectFormat);
70        }
71
72        let dtype = match &config.dtype {
73            Some(dtype) => dtype.clone(),
74            None => u.arbitrary()?,
75        };
76        let len = u.int_in_range(config.len.clone())?;
77
78        random_array(u, &dtype, Some(len)).map(ArbitraryArray)
79    }
80}
81
82fn split_number_into_parts(n: usize, parts: usize) -> Vec<usize> {
83    let reminder = n % parts;
84    let division = (n - reminder) / parts;
85    iter::repeat_n(division, parts - reminder)
86        .chain(iter::repeat_n(division + 1, reminder))
87        .collect()
88}
89
90/// Creates a random array with a random number of chunks.
91fn random_array(u: &mut Unstructured, dtype: &DType, len: Option<usize>) -> Result<ArrayRef> {
92    let num_chunks = u.int_in_range(1..=3)?;
93    let chunk_lens = len.map(|l| split_number_into_parts(l, num_chunks));
94    let mut chunks = (0..num_chunks)
95        .map(|i| {
96            let chunk_len = chunk_lens.as_ref().map(|c| c[i]);
97            random_array_chunk(u, dtype, chunk_len)
98        })
99        .collect::<Result<Vec<_>>>()?;
100
101    if chunks.len() == 1 {
102        Ok(chunks.remove(0))
103    } else {
104        let dtype = chunks[0].dtype().clone();
105        Ok(ChunkedArray::try_new(chunks, dtype)
106            .vortex_expect("operation should succeed in arbitrary impl")
107            .into_array())
108    }
109}
110
111/// Creates a random array chunk.
112fn random_array_chunk(
113    u: &mut Unstructured<'_>,
114    dtype: &DType,
115    chunk_len: Option<usize>,
116) -> Result<ArrayRef> {
117    match dtype {
118        DType::Null => Ok(NullArray::new(
119            chunk_len
120                .map(Ok)
121                .unwrap_or_else(|| u.int_in_range(0..=100))?,
122        )
123        .into_array()),
124        DType::Bool(n) => random_bool(u, *n, chunk_len),
125        DType::Primitive(ptype, n) => match ptype {
126            PType::U8 => random_primitive::<u8>(u, *n, chunk_len),
127            PType::U16 => random_primitive::<u16>(u, *n, chunk_len),
128            PType::U32 => random_primitive::<u32>(u, *n, chunk_len),
129            PType::U64 => random_primitive::<u64>(u, *n, chunk_len),
130            PType::I8 => random_primitive::<i8>(u, *n, chunk_len),
131            PType::I16 => random_primitive::<i16>(u, *n, chunk_len),
132            PType::I32 => random_primitive::<i32>(u, *n, chunk_len),
133            PType::I64 => random_primitive::<i64>(u, *n, chunk_len),
134            PType::F16 => {
135                let prim = random_primitive::<u16>(u, *n, chunk_len)?
136                    .as_::<Primitive>()
137                    .reinterpret_cast(PType::F16)
138                    .into_array();
139                Ok(prim)
140            }
141            PType::F32 => random_primitive::<f32>(u, *n, chunk_len),
142            PType::F64 => random_primitive::<f64>(u, *n, chunk_len),
143        },
144        d @ DType::Decimal(decimal, n) => {
145            let elem_len = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
146            match_each_decimal_value_type!(DecimalType::smallest_decimal_value_type(decimal), |D| {
147                let mut builder = DecimalBuilder::new_in::<D>(
148                    *decimal,
149                    *n,
150                    vortex_buffer::BufferAllocatorRef::static_ref(),
151                );
152                for _i in 0..elem_len {
153                    let random_decimal = random_scalar(u, d)?;
154                    builder.append_scalar(&random_decimal).vortex_expect(
155                        "was somehow unable to append a decimal to a decimal builder",
156                    );
157                }
158                Ok(builder.finish())
159            })
160        }
161        DType::Utf8(n) => random_string(u, *n, chunk_len),
162        DType::Binary(n) => random_bytes(u, *n, chunk_len),
163        DType::List(elem_dtype, null) => random_list(u, elem_dtype, *null, chunk_len),
164        DType::FixedSizeList(elem_dtype, list_size, null) => {
165            random_fixed_size_list(u, elem_dtype, *list_size, *null, chunk_len)
166        }
167        DType::Map(map_dtype, nullability) => {
168            random_map(u, map_dtype.clone(), *nullability, chunk_len)
169        }
170        DType::Struct(sdt, n) => {
171            let first_array = sdt
172                .fields()
173                .next()
174                .map(|d| random_array(u, &d, chunk_len))
175                .transpose()?;
176            let resolved_len = first_array
177                .as_ref()
178                .map(|a| a.len())
179                .or(chunk_len)
180                .map(Ok)
181                .unwrap_or_else(|| u.int_in_range(0..=100))?;
182            let children = first_array
183                .into_iter()
184                .map(Ok)
185                .chain(
186                    sdt.fields()
187                        .skip(1)
188                        .map(|d| random_array(u, &d, Some(resolved_len))),
189                )
190                .collect::<Result<Vec<_>>>()?;
191            Ok(StructArray::try_new(
192                sdt.names().clone(),
193                children,
194                resolved_len,
195                random_validity(u, *n, resolved_len)?,
196            )
197            .vortex_expect("operation should succeed in arbitrary impl")
198            .into_array())
199        }
200        DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
201        DType::Variant(_) => {
202            unimplemented!("Variant arrays are not implemented")
203        }
204        DType::Extension(..) => {
205            unimplemented!("Extension arrays are not implemented")
206        }
207    }
208}
209
210fn random_map(
211    u: &mut Unstructured,
212    map_dtype: MapDType,
213    nullability: Nullability,
214    chunk_len: Option<usize>,
215) -> Result<ArrayRef> {
216    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
217    let key_dtype = map_dtype.key_dtype();
218    let value_dtype = map_dtype.value_dtype();
219    let dtype = DType::Map(map_dtype.clone(), nullability);
220    let mut builder = MapBuilder::<u64, u64>::with_capacity_in(
221        map_dtype,
222        nullability,
223        array_length,
224        vortex_buffer::BufferAllocatorRef::static_ref(),
225    );
226
227    for _ in 0..array_length {
228        if nullability == Nullability::Nullable && u.arbitrary::<bool>()? {
229            builder.append_null();
230        } else {
231            let entry_count = u.int_in_range(0..=20)?;
232            let entries = (0..entry_count)
233                .map(|_| {
234                    let key = random_scalar(u, &key_dtype)?;
235                    let value = random_scalar(u, &value_dtype)?;
236                    Ok((key, value))
237                })
238                .collect::<Result<Vec<_>>>()?;
239            let scalar = Scalar::try_map(dtype.clone(), entries)
240                .vortex_expect("generated map scalar should be valid");
241            builder
242                .append_scalar(&scalar)
243                .vortex_expect("generated map scalar should append");
244        }
245    }
246
247    Ok(builder.finish_into_map().into_array())
248}
249
250/// Creates a random fixed-size list array.
251///
252/// If the `chunk_len` is specified, the length of the array will be equal to the chunk length.
253fn random_fixed_size_list(
254    u: &mut Unstructured,
255    elem_dtype: &Arc<DType>,
256    list_size: u32,
257    null: Nullability,
258    chunk_len: Option<usize>,
259) -> Result<ArrayRef> {
260    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
261
262    let mut builder = FixedSizeListBuilder::with_capacity_in(
263        Arc::clone(elem_dtype),
264        list_size,
265        null,
266        array_length,
267        vortex_buffer::BufferAllocatorRef::static_ref(),
268    );
269
270    for _ in 0..array_length {
271        if null == Nullability::Nullable && u.arbitrary::<bool>()? {
272            builder.append_null();
273        } else {
274            builder
275                .append_value(random_list_scalar(u, elem_dtype, list_size, null)?.as_list())
276                .vortex_expect("can append value");
277        }
278    }
279
280    Ok(builder.finish())
281}
282
283/// Creates a random list array.
284///
285/// If the `chunk_len` is specified, the length of the array will be equal to the chunk length.
286fn random_list(
287    u: &mut Unstructured,
288    elem_dtype: &Arc<DType>,
289    null: Nullability,
290    chunk_len: Option<usize>,
291) -> Result<ArrayRef> {
292    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
293    // Worst-case total elements: each list can have up to 20 elements.
294    let max_total_elements = array_length as u64 * 20;
295
296    match u.int_in_range(0..=3)? {
297        0 if i32::max_value_as_u64() >= max_total_elements => {
298            random_list_with_offset_type::<i32>(u, elem_dtype, null, array_length)
299        }
300        1 if u32::max_value_as_u64() >= max_total_elements => {
301            random_list_with_offset_type::<u32>(u, elem_dtype, null, array_length)
302        }
303        // i64 and u64 always fit; also the fallback for when narrower types don't.
304        _ => {
305            if u.arbitrary::<bool>()? {
306                random_list_with_offset_type::<i64>(u, elem_dtype, null, array_length)
307            } else {
308                random_list_with_offset_type::<u64>(u, elem_dtype, null, array_length)
309            }
310        }
311    }
312}
313
314/// Creates a random list array with the given [`OffsetBuilderPType`] for the internal offsets child.
315fn random_list_with_offset_type<O: OffsetBuilderPType>(
316    u: &mut Unstructured,
317    elem_dtype: &Arc<DType>,
318    null: Nullability,
319    array_length: usize,
320) -> Result<ArrayRef> {
321    let mut builder = ListViewBuilder::<O, O>::with_capacity_in(
322        Arc::clone(elem_dtype),
323        null,
324        array_length,
325        10,
326        vortex_buffer::BufferAllocatorRef::static_ref(),
327    );
328
329    for _ in 0..array_length {
330        if null == Nullability::Nullable && u.arbitrary::<bool>()? {
331            builder.append_null();
332        } else {
333            let list_size = u.int_in_range(0..=20)?;
334            builder
335                .append_value(random_list_scalar(u, elem_dtype, list_size, null)?.as_list())
336                .vortex_expect("can append value");
337        }
338    }
339
340    Ok(builder.finish())
341}
342
343/// Creates a random list scalar with the specified list size.
344fn random_list_scalar(
345    u: &mut Unstructured,
346    elem_dtype: &Arc<DType>,
347    list_size: u32,
348    null: Nullability,
349) -> Result<Scalar> {
350    let elems = (0..list_size)
351        .map(|_| random_scalar(u, elem_dtype))
352        .collect::<Result<Vec<_>>>()?;
353    Ok(Scalar::list(Arc::clone(elem_dtype), elems, null))
354}
355
356fn random_string(
357    u: &mut Unstructured,
358    nullability: Nullability,
359    len: Option<usize>,
360) -> Result<ArrayRef> {
361    match nullability {
362        Nullability::NonNullable => {
363            let v = arbitrary_vec_of_len::<String>(u, len)?;
364            Ok(match u.int_in_range(0..=1)? {
365                0 => VarBinArray::from_vec(v, DType::Utf8(Nullability::NonNullable)).into_array(),
366                1 => VarBinViewArray::from_iter_str(v).into_array(),
367                _ => unreachable!(),
368            })
369        }
370        Nullability::Nullable => {
371            let v = arbitrary_vec_of_len::<Option<String>>(u, len)?;
372            Ok(match u.int_in_range(0..=1)? {
373                0 => VarBinArray::from_iter(v, DType::Utf8(Nullability::Nullable)).into_array(),
374                1 => VarBinViewArray::from_iter_nullable_str(v).into_array(),
375                _ => unreachable!(),
376            })
377        }
378    }
379}
380
381fn random_bytes(
382    u: &mut Unstructured,
383    nullability: Nullability,
384    len: Option<usize>,
385) -> Result<ArrayRef> {
386    match nullability {
387        Nullability::NonNullable => {
388            let v = arbitrary_vec_of_len::<Vec<u8>>(u, len)?;
389            Ok(match u.int_in_range(0..=1)? {
390                0 => VarBinArray::from_vec(v, DType::Binary(Nullability::NonNullable)).into_array(),
391                1 => VarBinViewArray::from_iter_bin(v).into_array(),
392                _ => unreachable!(),
393            })
394        }
395        Nullability::Nullable => {
396            let v = arbitrary_vec_of_len::<Option<Vec<u8>>>(u, len)?;
397            Ok(match u.int_in_range(0..=1)? {
398                0 => VarBinArray::from_iter(v, DType::Binary(Nullability::Nullable)).into_array(),
399                1 => VarBinViewArray::from_iter_nullable_bin(v).into_array(),
400                _ => unreachable!(),
401            })
402        }
403    }
404}
405
406fn random_primitive<'a, T: Arbitrary<'a> + NativePType>(
407    u: &mut Unstructured<'a>,
408    nullability: Nullability,
409    len: Option<usize>,
410) -> Result<ArrayRef> {
411    let v = arbitrary_vec_of_len::<T>(u, len)?;
412    let validity = random_validity(u, nullability, v.len())?;
413    Ok(PrimitiveArray::new(Buffer::copy_from(v), validity).into_array())
414}
415
416fn random_bool(
417    u: &mut Unstructured,
418    nullability: Nullability,
419    len: Option<usize>,
420) -> Result<ArrayRef> {
421    let v = arbitrary_vec_of_len(u, len)?;
422    let validity = random_validity(u, nullability, v.len())?;
423    Ok(BoolArray::new(BitBuffer::from(v), validity).into_array())
424}
425
426pub fn random_validity(
427    u: &mut Unstructured,
428    nullability: Nullability,
429    len: usize,
430) -> Result<Validity> {
431    match nullability {
432        Nullability::NonNullable => Ok(Validity::NonNullable),
433        Nullability::Nullable => Ok(match u.int_in_range(0..=2)? {
434            0 => Validity::AllValid,
435            1 => Validity::AllInvalid,
436            2 => Validity::from_iter(arbitrary_vec_of_len::<bool>(u, Some(len))?),
437            _ => unreachable!(),
438        }),
439    }
440}
441
442fn arbitrary_vec_of_len<'a, T: Arbitrary<'a>>(
443    u: &mut Unstructured<'a>,
444    len: Option<usize>,
445) -> Result<Vec<T>> {
446    len.map(|l| (0..l).map(|_| T::arbitrary(u)).collect::<Result<Vec<_>>>())
447        .unwrap_or_else(|| Vec::<T>::arbitrary(u))
448}