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(..) | DType::Variant(_) | DType::Extension(..) => Err(IncorrectFormat),
201    }
202}
203
204fn random_map(
205    u: &mut Unstructured,
206    map_dtype: MapDType,
207    nullability: Nullability,
208    chunk_len: Option<usize>,
209) -> Result<ArrayRef> {
210    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
211    let key_dtype = map_dtype.key_dtype();
212    let value_dtype = map_dtype.value_dtype();
213    let dtype = DType::Map(map_dtype.clone(), nullability);
214    let mut builder = MapBuilder::<u64, u64>::with_capacity_in(
215        map_dtype,
216        nullability,
217        array_length,
218        vortex_buffer::BufferAllocatorRef::static_ref(),
219    );
220
221    for _ in 0..array_length {
222        if nullability == Nullability::Nullable && u.arbitrary::<bool>()? {
223            builder.append_null();
224        } else {
225            let entry_count = u.int_in_range(0..=20)?;
226            let entries = (0..entry_count)
227                .map(|_| {
228                    let key = random_scalar(u, &key_dtype)?;
229                    let value = random_scalar(u, &value_dtype)?;
230                    Ok((key, value))
231                })
232                .collect::<Result<Vec<_>>>()?;
233            let scalar = Scalar::try_map(dtype.clone(), entries)
234                .vortex_expect("generated map scalar should be valid");
235            builder
236                .append_scalar(&scalar)
237                .vortex_expect("generated map scalar should append");
238        }
239    }
240
241    Ok(builder.finish_into_map().into_array())
242}
243
244/// Creates a random fixed-size list array.
245///
246/// If the `chunk_len` is specified, the length of the array will be equal to the chunk length.
247fn random_fixed_size_list(
248    u: &mut Unstructured,
249    elem_dtype: &Arc<DType>,
250    list_size: u32,
251    null: Nullability,
252    chunk_len: Option<usize>,
253) -> Result<ArrayRef> {
254    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
255
256    let mut builder = FixedSizeListBuilder::with_capacity_in(
257        Arc::clone(elem_dtype),
258        list_size,
259        null,
260        array_length,
261        vortex_buffer::BufferAllocatorRef::static_ref(),
262    );
263
264    for _ in 0..array_length {
265        if null == Nullability::Nullable && u.arbitrary::<bool>()? {
266            builder.append_null();
267        } else {
268            builder
269                .append_value(random_list_scalar(u, elem_dtype, list_size, null)?.as_list())
270                .vortex_expect("can append value");
271        }
272    }
273
274    Ok(builder.finish())
275}
276
277/// Creates a random list array.
278///
279/// If the `chunk_len` is specified, the length of the array will be equal to the chunk length.
280fn random_list(
281    u: &mut Unstructured,
282    elem_dtype: &Arc<DType>,
283    null: Nullability,
284    chunk_len: Option<usize>,
285) -> Result<ArrayRef> {
286    let array_length = chunk_len.unwrap_or(u.int_in_range(0..=20)?);
287    // Worst-case total elements: each list can have up to 20 elements.
288    let max_total_elements = array_length as u64 * 20;
289
290    match u.int_in_range(0..=3)? {
291        0 if i32::max_value_as_u64() >= max_total_elements => {
292            random_list_with_offset_type::<i32>(u, elem_dtype, null, array_length)
293        }
294        1 if u32::max_value_as_u64() >= max_total_elements => {
295            random_list_with_offset_type::<u32>(u, elem_dtype, null, array_length)
296        }
297        // i64 and u64 always fit; also the fallback for when narrower types don't.
298        _ => {
299            if u.arbitrary::<bool>()? {
300                random_list_with_offset_type::<i64>(u, elem_dtype, null, array_length)
301            } else {
302                random_list_with_offset_type::<u64>(u, elem_dtype, null, array_length)
303            }
304        }
305    }
306}
307
308/// Creates a random list array with the given [`OffsetBuilderPType`] for the internal offsets child.
309fn random_list_with_offset_type<O: OffsetBuilderPType>(
310    u: &mut Unstructured,
311    elem_dtype: &Arc<DType>,
312    null: Nullability,
313    array_length: usize,
314) -> Result<ArrayRef> {
315    let mut builder = ListViewBuilder::<O, O>::with_capacity_in(
316        Arc::clone(elem_dtype),
317        null,
318        array_length,
319        10,
320        vortex_buffer::BufferAllocatorRef::static_ref(),
321    );
322
323    for _ in 0..array_length {
324        if null == Nullability::Nullable && u.arbitrary::<bool>()? {
325            builder.append_null();
326        } else {
327            let list_size = u.int_in_range(0..=20)?;
328            builder
329                .append_value(random_list_scalar(u, elem_dtype, list_size, null)?.as_list())
330                .vortex_expect("can append value");
331        }
332    }
333
334    Ok(builder.finish())
335}
336
337/// Creates a random list scalar with the specified list size.
338fn random_list_scalar(
339    u: &mut Unstructured,
340    elem_dtype: &Arc<DType>,
341    list_size: u32,
342    null: Nullability,
343) -> Result<Scalar> {
344    let elems = (0..list_size)
345        .map(|_| random_scalar(u, elem_dtype))
346        .collect::<Result<Vec<_>>>()?;
347    Ok(Scalar::list(Arc::clone(elem_dtype), elems, null))
348}
349
350fn random_string(
351    u: &mut Unstructured,
352    nullability: Nullability,
353    len: Option<usize>,
354) -> Result<ArrayRef> {
355    match nullability {
356        Nullability::NonNullable => {
357            let v = arbitrary_vec_of_len::<String>(u, len)?;
358            Ok(match u.int_in_range(0..=1)? {
359                0 => VarBinArray::from_vec(v, DType::Utf8(Nullability::NonNullable)).into_array(),
360                1 => VarBinViewArray::from_iter_str(v).into_array(),
361                _ => unreachable!(),
362            })
363        }
364        Nullability::Nullable => {
365            let v = arbitrary_vec_of_len::<Option<String>>(u, len)?;
366            Ok(match u.int_in_range(0..=1)? {
367                0 => VarBinArray::from_iter(v, DType::Utf8(Nullability::Nullable)).into_array(),
368                1 => VarBinViewArray::from_iter_nullable_str(v).into_array(),
369                _ => unreachable!(),
370            })
371        }
372    }
373}
374
375fn random_bytes(
376    u: &mut Unstructured,
377    nullability: Nullability,
378    len: Option<usize>,
379) -> Result<ArrayRef> {
380    match nullability {
381        Nullability::NonNullable => {
382            let v = arbitrary_vec_of_len::<Vec<u8>>(u, len)?;
383            Ok(match u.int_in_range(0..=1)? {
384                0 => VarBinArray::from_vec(v, DType::Binary(Nullability::NonNullable)).into_array(),
385                1 => VarBinViewArray::from_iter_bin(v).into_array(),
386                _ => unreachable!(),
387            })
388        }
389        Nullability::Nullable => {
390            let v = arbitrary_vec_of_len::<Option<Vec<u8>>>(u, len)?;
391            Ok(match u.int_in_range(0..=1)? {
392                0 => VarBinArray::from_iter(v, DType::Binary(Nullability::Nullable)).into_array(),
393                1 => VarBinViewArray::from_iter_nullable_bin(v).into_array(),
394                _ => unreachable!(),
395            })
396        }
397    }
398}
399
400fn random_primitive<'a, T: Arbitrary<'a> + NativePType>(
401    u: &mut Unstructured<'a>,
402    nullability: Nullability,
403    len: Option<usize>,
404) -> Result<ArrayRef> {
405    let v = arbitrary_vec_of_len::<T>(u, len)?;
406    let validity = random_validity(u, nullability, v.len())?;
407    Ok(PrimitiveArray::new(Buffer::copy_from(v), validity).into_array())
408}
409
410fn random_bool(
411    u: &mut Unstructured,
412    nullability: Nullability,
413    len: Option<usize>,
414) -> Result<ArrayRef> {
415    let v = arbitrary_vec_of_len(u, len)?;
416    let validity = random_validity(u, nullability, v.len())?;
417    Ok(BoolArray::new(BitBuffer::from(v), validity).into_array())
418}
419
420pub fn random_validity(
421    u: &mut Unstructured,
422    nullability: Nullability,
423    len: usize,
424) -> Result<Validity> {
425    match nullability {
426        Nullability::NonNullable => Ok(Validity::NonNullable),
427        Nullability::Nullable => Ok(match u.int_in_range(0..=2)? {
428            0 => Validity::AllValid,
429            1 => Validity::AllInvalid,
430            2 => Validity::from_iter(arbitrary_vec_of_len::<bool>(u, Some(len))?),
431            _ => unreachable!(),
432        }),
433    }
434}
435
436fn arbitrary_vec_of_len<'a, T: Arbitrary<'a>>(
437    u: &mut Unstructured<'a>,
438    len: Option<usize>,
439) -> Result<Vec<T>> {
440    len.map(|l| (0..l).map(|_| T::arbitrary(u)).collect::<Result<Vec<_>>>())
441        .unwrap_or_else(|| Vec::<T>::arbitrary(u))
442}