Skip to main content

arrow/util/
bench_util.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Utils to make benchmarking easier
19
20use crate::array::*;
21use crate::datatypes::*;
22use crate::util::test_util::seedable_rng;
23use arrow_buffer::{Buffer, IntervalMonthDayNano, NullBuffer};
24use arrow_schema::Field;
25use half::f16;
26use rand::RngExt;
27use rand::SeedableRng;
28use rand::distr::uniform::SampleUniform;
29use rand::rng;
30use rand::{
31    distr::{Alphanumeric, Distribution, SampleString, StandardUniform},
32    prelude::StdRng,
33};
34use std::ops::Range;
35use std::sync::Arc;
36
37fn f16_random(rng: &mut StdRng) -> f16 {
38    // Otherwise it can round up to 1.0 even though we should generate in [0, 1)
39    // See: https://github.com/VoidStarKat/half-rs/issues/152
40    let one_next_down = f16::from_bits(0x3BFFu16); // 0.9995117
41    f16::from_f32(rng.random::<f32>()).clamp(f16::ZERO, one_next_down)
42}
43
44/// Creates an random (but fixed-seeded) array of a given size and null density
45pub fn create_primitive_array<T>(size: usize, null_density: f32) -> PrimitiveArray<T>
46where
47    T: ArrowPrimitiveType,
48    StandardUniform: Distribution<T::Native>,
49{
50    let mut rng = seedable_rng();
51
52    (0..size)
53        .map(|_| {
54            if rng.random::<f32>() < null_density {
55                None
56            } else {
57                Some(rng.random())
58            }
59        })
60        .collect()
61}
62
63/// Same as [`create_primitive_array`] but specialized for f16 since it doesn't
64/// implement the required rand traits.
65///
66/// This may be deprecated/removed in the future if half updates to the required
67/// rand version.
68pub fn create_nullable_f16_array(size: usize, null_density: f32) -> Float16Array {
69    let mut rng = seedable_rng();
70
71    (0..size)
72        .map(|_| {
73            if rng.random::<f32>() < null_density {
74                None
75            } else {
76                Some(f16_random(&mut rng))
77            }
78        })
79        .collect()
80}
81
82/// Creates an random (but fixed-seeded) array of a given size and null density,
83/// all the values located in the given range
84pub fn create_primitive_array_range<T>(
85    size: usize,
86    null_density: f32,
87    value_range: Range<T::Native>,
88) -> PrimitiveArray<T>
89where
90    T: ArrowPrimitiveType,
91    StandardUniform: Distribution<T::Native>,
92    T::Native: SampleUniform,
93{
94    let mut rng = seedable_rng();
95
96    (0..size)
97        .map(|_| {
98            if rng.random::<f32>() < null_density {
99                None
100            } else {
101                Some(rng.random_range(value_range.clone()))
102            }
103        })
104        .collect()
105}
106
107/// Creates a [`PrimitiveArray`] of a given `size` and `null_density`
108/// filling it with random numbers generated using the provided `seed`.
109pub fn create_primitive_array_with_seed<T>(
110    size: usize,
111    null_density: f32,
112    seed: u64,
113) -> PrimitiveArray<T>
114where
115    T: ArrowPrimitiveType,
116    StandardUniform: Distribution<T::Native>,
117{
118    let mut rng = StdRng::seed_from_u64(seed);
119
120    (0..size)
121        .map(|_| {
122            if rng.random::<f32>() < null_density {
123                None
124            } else {
125                Some(rng.random())
126            }
127        })
128        .collect()
129}
130
131/// Creates a [`PrimitiveArray`] of a given `size` and `null_density`
132/// filling it with random [`IntervalMonthDayNano`] generated using the provided `seed`.
133pub fn create_month_day_nano_array_with_seed(
134    size: usize,
135    null_density: f32,
136    seed: u64,
137) -> IntervalMonthDayNanoArray {
138    let mut rng = StdRng::seed_from_u64(seed);
139
140    (0..size)
141        .map(|_| {
142            if rng.random::<f32>() < null_density {
143                None
144            } else {
145                Some(IntervalMonthDayNano::new(
146                    rng.random(),
147                    rng.random(),
148                    rng.random(),
149                ))
150            }
151        })
152        .collect()
153}
154
155/// Creates a random (but fixed-seeded) array of a given size and null density
156pub fn create_boolean_array(size: usize, null_density: f32, true_density: f32) -> BooleanArray
157where
158    StandardUniform: Distribution<bool>,
159{
160    let mut rng = seedable_rng();
161    (0..size)
162        .map(|_| {
163            if rng.random::<f32>() < null_density {
164                None
165            } else {
166                let value = rng.random::<f32>() < true_density;
167                Some(value)
168            }
169        })
170        .collect()
171}
172
173/// Creates a random array of a given size and null density based on the provided seed
174pub fn create_boolean_array_with_seed(
175    size: usize,
176    null_density: f32,
177    true_density: f32,
178    seed: u64,
179) -> BooleanArray
180where
181    StandardUniform: Distribution<bool>,
182{
183    let mut rng = StdRng::seed_from_u64(seed);
184    (0..size)
185        .map(|_| {
186            if rng.random::<f32>() < null_density {
187                None
188            } else {
189                let value = rng.random::<f32>() < true_density;
190                Some(value)
191            }
192        })
193        .collect()
194}
195
196/// Creates a random (but fixed-seeded) string array of a given size and null density.
197///
198/// Strings have a random length
199/// between 0 and 400 alphanumeric characters. `0..400` is chosen to cover a wide range of common string lengths,
200/// which have a dramatic impact on performance of some queries, e.g. LIKE/ILIKE/regex.
201pub fn create_string_array<Offset: OffsetSizeTrait>(
202    size: usize,
203    null_density: f32,
204) -> GenericStringArray<Offset> {
205    create_string_array_with_max_len(size, null_density, 400)
206}
207
208/// Creates longer string array with same prefix, the prefix should be larger than 4 bytes,
209/// and the string length should be larger than 12 bytes
210/// so that we can compare the performance with StringViewArray, because StringViewArray has 4 bytes inline for view
211pub fn create_longer_string_array_with_same_prefix<Offset: OffsetSizeTrait>(
212    size: usize,
213    null_density: f32,
214) -> GenericStringArray<Offset> {
215    create_string_array_with_len_range_and_prefix(size, null_density, 13, 100, "prefix_")
216}
217
218/// Creates longer string view array with same prefix, the prefix should be larger than 4 bytes,
219/// and the string length should be larger than 12 bytes
220/// so that we can compare the StringArray performance with StringViewArray, because StringViewArray has 4 bytes inline for view
221pub fn create_longer_string_view_array_with_same_prefix(
222    size: usize,
223    null_density: f32,
224) -> StringViewArray {
225    create_string_view_array_with_len_range_and_prefix(size, null_density, 13, 100, "prefix_")
226}
227
228fn create_string_array_with_len_range_and_prefix<Offset: OffsetSizeTrait>(
229    size: usize,
230    null_density: f32,
231    min_str_len: usize,
232    max_str_len: usize,
233    prefix: &str,
234) -> GenericStringArray<Offset> {
235    create_string_array_with_len_range_and_prefix_and_seed(
236        size,
237        null_density,
238        min_str_len,
239        max_str_len,
240        prefix,
241        42,
242    )
243}
244
245/// Creates a random [`GenericStringArray`] of a given `size` and `null_density`
246/// filling it with random strings with lengths in the specified range,
247/// all starting with the provided `prefix`, generated using the provided `seed`.
248///
249/// # Panics
250///
251/// Panics if `min_str_len > max_str_len` or `prefix.len() > max_str_len`
252pub fn create_string_array_with_len_range_and_prefix_and_seed<Offset: OffsetSizeTrait>(
253    size: usize,
254    null_density: f32,
255    min_str_len: usize,
256    max_str_len: usize,
257    prefix: &str,
258    seed: u64,
259) -> GenericStringArray<Offset> {
260    assert!(
261        min_str_len <= max_str_len,
262        "min_str_len must be <= max_str_len"
263    );
264    assert!(
265        prefix.len() <= max_str_len,
266        "Prefix length must be <= max_str_len"
267    );
268
269    let rng = &mut StdRng::seed_from_u64(seed);
270    (0..size)
271        .map(|_| {
272            if rng.random::<f32>() < null_density {
273                None
274            } else {
275                let remaining_len = rng.random_range(
276                    min_str_len.saturating_sub(prefix.len())..=(max_str_len - prefix.len()),
277                );
278
279                let mut value = prefix.to_string();
280                value.extend(
281                    rng.sample_iter(&Alphanumeric)
282                        .take(remaining_len)
283                        .map(char::from),
284                );
285
286                Some(value)
287            }
288        })
289        .collect()
290}
291/// Creates a string view array of a given range, null density and length
292///
293/// Arguments:
294/// - `size`: number of  string view array
295/// - `null_density`: density of nulls in the string view array
296/// - `range`: range size of each string in the string view array
297/// - `seed`: seed for the random number generator
298pub fn create_string_view_array_with_len_range_and_seed(
299    size: usize,
300    null_density: f32,
301    range: Range<usize>,
302    seed: u64,
303) -> StringViewArray {
304    let rng = &mut StdRng::seed_from_u64(seed);
305    (0..size)
306        .map(|_| {
307            if rng.random::<f32>() < null_density {
308                None
309            } else {
310                let str_len = rng.random_range(range.clone());
311                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
312                let value = String::from_utf8(value).unwrap();
313                Some(value)
314            }
315        })
316        .collect()
317}
318
319fn create_string_view_array_with_len_range_and_prefix(
320    size: usize,
321    null_density: f32,
322    min_str_len: usize,
323    max_str_len: usize,
324    prefix: &str,
325) -> StringViewArray {
326    assert!(
327        min_str_len <= max_str_len,
328        "min_str_len must be <= max_str_len"
329    );
330    assert!(
331        prefix.len() <= max_str_len,
332        "Prefix length must be <= max_str_len"
333    );
334
335    let rng = &mut seedable_rng();
336    (0..size)
337        .map(|_| {
338            if rng.random::<f32>() < null_density {
339                None
340            } else {
341                let remaining_len = rng.random_range(
342                    min_str_len.saturating_sub(prefix.len())..=(max_str_len - prefix.len()),
343                );
344
345                let mut value = prefix.to_string();
346                value.extend(
347                    rng.sample_iter(&Alphanumeric)
348                        .take(remaining_len)
349                        .map(char::from),
350                );
351
352                Some(value)
353            }
354        })
355        .collect()
356}
357
358/// Creates a random (but fixed-seeded) array of rand size with a given max size, null density and length
359pub fn create_string_array_with_max_len<Offset: OffsetSizeTrait>(
360    size: usize,
361    null_density: f32,
362    max_str_len: usize,
363) -> GenericStringArray<Offset> {
364    let rng = &mut seedable_rng();
365    (0..size)
366        .map(|_| {
367            if rng.random::<f32>() < null_density {
368                None
369            } else {
370                let str_len = rng.random_range(0..max_str_len);
371                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
372                let value = String::from_utf8(value).unwrap();
373                Some(value)
374            }
375        })
376        .collect()
377}
378
379/// Creates a random (but fixed-seeded) array of a given size, null density and length
380pub fn create_string_array_with_len<Offset: OffsetSizeTrait>(
381    size: usize,
382    null_density: f32,
383    str_len: usize,
384) -> GenericStringArray<Offset> {
385    let rng = &mut seedable_rng();
386
387    (0..size)
388        .map(|_| {
389            if rng.random::<f32>() < null_density {
390                None
391            } else {
392                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
393                let value = String::from_utf8(value).unwrap();
394                Some(value)
395            }
396        })
397        .collect()
398}
399
400/// Creates a random (but fixed-seeded) string view array of a given size and null density.
401///
402/// See `create_string_array` above for more details.
403pub fn create_string_view_array(size: usize, null_density: f32) -> StringViewArray {
404    create_string_view_array_with_max_len(size, null_density, 400)
405}
406
407/// Creates a random (but fixed-seeded) array of rand size with a given max size, null density and length
408pub fn create_string_view_array_with_max_len(
409    size: usize,
410    null_density: f32,
411    max_str_len: usize,
412) -> StringViewArray {
413    let rng = &mut seedable_rng();
414    (0..size)
415        .map(|_| {
416            if rng.random::<f32>() < null_density {
417                None
418            } else {
419                let str_len = rng.random_range(0..max_str_len);
420                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
421                let value = String::from_utf8(value).unwrap();
422                Some(value)
423            }
424        })
425        .collect()
426}
427
428/// Creates a random (but fixed-seeded) array of a given size, null density and length
429pub fn create_string_view_array_with_fixed_len(
430    size: usize,
431    null_density: f32,
432    str_len: usize,
433) -> StringViewArray {
434    let rng = &mut seedable_rng();
435    (0..size)
436        .map(|_| {
437            if rng.random::<f32>() < null_density {
438                None
439            } else {
440                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
441                let value = String::from_utf8(value).unwrap();
442                Some(value)
443            }
444        })
445        .collect()
446}
447
448/// Creates a random (but fixed-seeded) array of a given size, null density and length
449pub fn create_string_view_array_with_len(
450    size: usize,
451    null_density: f32,
452    str_len: usize,
453    mixed: bool,
454) -> StringViewArray {
455    let rng = &mut seedable_rng();
456
457    let mut lengths = Vec::with_capacity(size);
458
459    // if mixed, we creates first half that string length small than 12 bytes and second half large than 12 bytes
460    if mixed {
461        for _ in 0..size / 2 {
462            lengths.push(rng.random_range(1..12));
463        }
464        for _ in size / 2..size {
465            lengths.push(rng.random_range(12..=std::cmp::max(30, str_len)));
466        }
467    } else {
468        lengths.resize(size, str_len);
469    }
470
471    lengths
472        .into_iter()
473        .map(|len| {
474            if rng.random::<f32>() < null_density {
475                None
476            } else {
477                let value: Vec<u8> = rng.sample_iter(&Alphanumeric).take(len).collect();
478                Some(String::from_utf8(value).unwrap())
479            }
480        })
481        .collect()
482}
483
484/// Creates an random (but fixed-seeded) array of a given size and null density
485/// consisting of random 4 character alphanumeric strings
486pub fn create_string_dict_array<K: ArrowDictionaryKeyType>(
487    size: usize,
488    null_density: f32,
489    str_len: usize,
490) -> DictionaryArray<K> {
491    let rng = &mut seedable_rng();
492
493    let data: Vec<_> = (0..size)
494        .map(|_| {
495            if rng.random::<f32>() < null_density {
496                None
497            } else {
498                let value = rng.sample_iter(&Alphanumeric).take(str_len).collect();
499                let value = String::from_utf8(value).unwrap();
500                Some(value)
501            }
502        })
503        .collect();
504
505    data.iter().map(|x| x.as_deref()).collect()
506}
507
508/// Create a List/LargeList Array  of primitive values
509///
510/// Arguments:
511/// - `size`: number of lists in the array
512/// - `null_density`: density of nulls in the list array
513/// - `list_null_density`: density of nulls in the primitive arrays inside the lists
514/// - `max_list_size`: maximum size of each list (actual size is random between 0 and max_list_size)
515/// - `seed`: seed for the random number generator
516pub fn create_primitive_list_array_with_seed<O, T>(
517    size: usize,
518    null_density: f32,
519    list_null_density: f32,
520    max_list_size: usize,
521    seed: u64,
522) -> GenericListArray<O>
523where
524    O: OffsetSizeTrait,
525    T: ArrowPrimitiveType,
526    StandardUniform: Distribution<T::Native>,
527{
528    let mut rng = StdRng::seed_from_u64(seed);
529
530    let values = (0..size).map(|_| {
531        if rng.random::<f32>() < null_density {
532            None
533        } else {
534            let list_size = rng.random_range(0..=max_list_size);
535            let list_values: Vec<Option<T::Native>> = (0..list_size)
536                .map(|_| {
537                    if rng.random::<f32>() < list_null_density {
538                        None
539                    } else {
540                        Some(rng.random())
541                    }
542                })
543                .collect();
544            Some(list_values)
545        }
546    });
547
548    GenericListArray::<O>::from_iter_primitive::<T, _, _>(values)
549}
550
551/// Create a List/LargeList Array of primitive values using a fixed seed
552///
553/// See [`create_primitive_list_array_with_seed`] for details on arguments.
554pub fn create_primitive_list_array<O, T>(
555    size: usize,
556    null_density: f32,
557    list_null_density: f32,
558    max_list_size: usize,
559) -> GenericListArray<O>
560where
561    O: OffsetSizeTrait,
562    T: ArrowPrimitiveType,
563    StandardUniform: Distribution<T::Native>,
564{
565    let mut rng = seedable_rng();
566
567    let values = (0..size).map(|_| {
568        if rng.random::<f32>() < null_density {
569            None
570        } else {
571            let list_size = rng.random_range(0..=max_list_size);
572            let list_values: Vec<Option<T::Native>> = (0..list_size)
573                .map(|_| {
574                    if rng.random::<f32>() < list_null_density {
575                        None
576                    } else {
577                        Some(rng.random())
578                    }
579                })
580                .collect();
581            Some(list_values)
582        }
583    });
584
585    GenericListArray::<O>::from_iter_primitive::<T, _, _>(values)
586}
587
588/// Create a ListViewArray of primitive values using a fixed seed
589///
590/// See [`create_primitive_list_array_with_seed`] for details on arguments.
591pub fn create_primitive_list_view_array<O, T>(
592    size: usize,
593    null_density: f32,
594    list_null_density: f32,
595    max_list_size: usize,
596) -> GenericListViewArray<O>
597where
598    T: ArrowPrimitiveType,
599    StandardUniform: Distribution<T::Native>,
600    O: OffsetSizeTrait,
601{
602    let mut rng = seedable_rng();
603
604    let values = (0..size).map(|_| {
605        if rng.random::<f32>() < null_density {
606            None
607        } else {
608            let list_size = rng.random_range(0..=max_list_size);
609            let list_values: Vec<Option<T::Native>> = (0..list_size)
610                .map(|_| {
611                    if rng.random::<f32>() < list_null_density {
612                        None
613                    } else {
614                        Some(rng.random())
615                    }
616                })
617                .collect();
618            Some(list_values)
619        }
620    });
621
622    GenericListViewArray::<O>::from_iter_primitive::<T, _, _>(values)
623}
624
625/// Create primitive run array for given logical and physical array lengths
626///
627/// # Panics
628///
629/// Panics if `logical_array_len < physical_array_len`
630pub fn create_primitive_run_array<R: RunEndIndexType, V: ArrowPrimitiveType>(
631    logical_array_len: usize,
632    physical_array_len: usize,
633) -> RunArray<R> {
634    assert!(logical_array_len >= physical_array_len);
635    // typical length of each run
636    let run_len = logical_array_len / physical_array_len;
637
638    // Some runs should have extra length
639    let mut run_len_extra = logical_array_len % physical_array_len;
640
641    let mut values: Vec<V::Native> = (0..physical_array_len)
642        .flat_map(|s| {
643            let mut take_len = run_len;
644            if run_len_extra > 0 {
645                take_len += 1;
646                run_len_extra -= 1;
647            }
648            std::iter::repeat_n(V::Native::from_usize(s).unwrap(), take_len)
649        })
650        .collect();
651    while values.len() < logical_array_len {
652        let last_val = values[values.len() - 1];
653        values.push(last_val);
654    }
655    let mut builder = PrimitiveRunBuilder::<R, V>::with_capacity(physical_array_len);
656    builder.extend(values.into_iter().map(Some));
657
658    builder.finish()
659}
660
661/// Create string array to be used by run array builder. The string array
662/// will result in run array with physical length of `physical_array_len`
663/// and logical length of `logical_array_len`
664///
665/// # Panics
666///
667/// Panics if `logical_array_len < physical_array_len`
668pub fn create_string_array_for_runs(
669    physical_array_len: usize,
670    logical_array_len: usize,
671    string_len: usize,
672) -> Vec<String> {
673    assert!(logical_array_len >= physical_array_len);
674    let mut rng = rng();
675
676    // typical length of each run
677    let run_len = logical_array_len / physical_array_len;
678
679    // Some runs should have extra length
680    let mut run_len_extra = logical_array_len % physical_array_len;
681
682    let mut values: Vec<String> = (0..physical_array_len)
683        .map(|_| (0..string_len).map(|_| rng.random::<char>()).collect())
684        .flat_map(|s| {
685            let mut take_len = run_len;
686            if run_len_extra > 0 {
687                take_len += 1;
688                run_len_extra -= 1;
689            }
690            std::iter::repeat_n(s, take_len)
691        })
692        .collect();
693    while values.len() < logical_array_len {
694        let last_val = values[values.len() - 1].clone();
695        values.push(last_val);
696    }
697    values
698}
699
700/// Creates an random (but fixed-seeded) binary array of a given size and null density
701pub fn create_binary_array<Offset: OffsetSizeTrait>(
702    size: usize,
703    null_density: f32,
704) -> GenericBinaryArray<Offset> {
705    create_binary_array_with_seed(
706        size,
707        null_density,
708        42, // bytes_seed
709        42, // bytes_length_seed
710    )
711}
712
713/// Creates a random [`GenericBinaryArray`] of a given `size` and `null_density`
714/// filling it with random bytes, generated using the provided `seed`s.
715///
716/// the `bytes_seed` is used to seed the RNG for generating the byte values,
717/// while the `bytes_length_seed` is used to seed the RNG for generating the length of an array item
718///
719/// These values can be the same as they are used to seed different RNGs internally.
720pub fn create_binary_array_with_seed<Offset: OffsetSizeTrait>(
721    size: usize,
722    null_density: f32,
723    bytes_seed: u64,
724    bytes_length_seed: u64,
725) -> GenericBinaryArray<Offset> {
726    let rng = &mut StdRng::seed_from_u64(bytes_seed);
727    let range_rng = &mut StdRng::seed_from_u64(bytes_length_seed);
728
729    (0..size)
730        .map(|_| {
731            if rng.random::<f32>() < null_density {
732                None
733            } else {
734                let value = rng
735                    .sample_iter::<u8, _>(StandardUniform)
736                    .take(range_rng.random_range(0..8))
737                    .collect::<Vec<u8>>();
738                Some(value)
739            }
740        })
741        .collect()
742}
743
744/// Creates a random [`GenericBinaryArray`] of a given `size` and `null_density`
745/// filling it with random bytes with lengths in the specified range,
746/// all starting with the provided `prefix`, generated using the provided `seed`.
747///
748///
749/// # Panics
750///
751/// Panics if `min_len > max_len` or `prefix.len() > max_len`
752pub fn create_binary_array_with_len_range_and_prefix_and_seed<Offset: OffsetSizeTrait>(
753    size: usize,
754    null_density: f32,
755    min_len: usize,
756    max_len: usize,
757    prefix: &[u8],
758    seed: u64,
759) -> GenericBinaryArray<Offset> {
760    assert!(min_len <= max_len, "min_len must be <= max_len");
761    assert!(prefix.len() <= max_len, "Prefix length must be <= max_len");
762
763    let rng = &mut StdRng::seed_from_u64(seed);
764    (0..size)
765        .map(|_| {
766            if rng.random::<f32>() < null_density {
767                None
768            } else {
769                let remaining_len = rng
770                    .random_range(min_len.saturating_sub(prefix.len())..=(max_len - prefix.len()));
771
772                let remaining = rng
773                    .sample_iter::<u8, _>(StandardUniform)
774                    .take(remaining_len);
775
776                let value = prefix.iter().copied().chain(remaining).collect::<Vec<u8>>();
777                Some(value)
778            }
779        })
780        .collect()
781}
782
783/// Creates an random (but fixed-seeded) array of a given size and null density
784pub fn create_fsb_array(size: usize, null_density: f32, value_len: usize) -> FixedSizeBinaryArray {
785    let rng = &mut seedable_rng();
786
787    FixedSizeBinaryArray::try_from_sparse_iter_with_size(
788        (0..size).map(|_| {
789            if rng.random::<f32>() < null_density {
790                None
791            } else {
792                let value = rng
793                    .sample_iter::<u8, _>(StandardUniform)
794                    .take(value_len)
795                    .collect::<Vec<u8>>();
796                Some(value)
797            }
798        }),
799        value_len as i32,
800    )
801    .unwrap()
802}
803
804/// Creates a random (but fixed-seeded) dictionary array of a given size and null density
805/// with the provided values array
806pub fn create_dict_from_values<K>(
807    size: usize,
808    null_density: f32,
809    values: &dyn Array,
810) -> DictionaryArray<K>
811where
812    K: ArrowDictionaryKeyType,
813    StandardUniform: Distribution<K::Native>,
814    K::Native: SampleUniform,
815{
816    let min_key = K::Native::from_usize(0).unwrap();
817    let max_key = K::Native::from_usize(values.len()).unwrap();
818    create_sparse_dict_from_values(size, null_density, values, min_key..max_key)
819}
820
821/// Creates a random (but fixed-seeded) dictionary array of a given size and null density
822/// with the provided values array and key range
823pub fn create_sparse_dict_from_values<K>(
824    size: usize,
825    null_density: f32,
826    values: &dyn Array,
827    key_range: Range<K::Native>,
828) -> DictionaryArray<K>
829where
830    K: ArrowDictionaryKeyType,
831    StandardUniform: Distribution<K::Native>,
832    K::Native: SampleUniform,
833{
834    let mut rng = seedable_rng();
835    let data_type =
836        DataType::Dictionary(Box::new(K::DATA_TYPE), Box::new(values.data_type().clone()));
837
838    let keys: Buffer = (0..size)
839        .map(|_| rng.random_range(key_range.clone()))
840        .collect();
841
842    let nulls: Option<Buffer> = (null_density != 0.).then(|| {
843        (0..size)
844            .map(|_| rng.random_bool((1.0 - null_density) as _))
845            .collect()
846    });
847
848    let data = ArrayDataBuilder::new(data_type)
849        .len(size)
850        .null_bit_buffer(nulls)
851        .add_buffer(keys)
852        .add_child_data(values.to_data())
853        .build()
854        .unwrap();
855
856    DictionaryArray::from(data)
857}
858
859/// Creates a random (but fixed-seeded) f16 array of a given size and nan-value density
860pub fn create_f16_array(size: usize, nan_density: f32) -> Float16Array {
861    let mut rng = seedable_rng();
862
863    (0..size)
864        .map(|_| {
865            if rng.random::<f32>() < nan_density {
866                Some(f16::NAN)
867            } else {
868                Some(f16_random(&mut rng))
869            }
870        })
871        .collect()
872}
873
874/// Creates a random (but fixed-seeded) f32 array of a given size and nan-value density
875pub fn create_f32_array(size: usize, nan_density: f32) -> Float32Array {
876    let mut rng = seedable_rng();
877
878    (0..size)
879        .map(|_| {
880            if rng.random::<f32>() < nan_density {
881                Some(f32::NAN)
882            } else {
883                Some(rng.random())
884            }
885        })
886        .collect()
887}
888
889/// Creates a random (but fixed-seeded) f64 array of a given size and nan-value density
890pub fn create_f64_array(size: usize, nan_density: f32) -> Float64Array {
891    let mut rng = seedable_rng();
892
893    (0..size)
894        .map(|_| {
895            if rng.random::<f32>() < nan_density {
896                Some(f64::NAN)
897            } else {
898                Some(rng.random())
899            }
900        })
901        .collect()
902}
903
904/// Creates a random f64 array of a given size and nan-value density based on a given seed
905pub fn create_f64_array_with_seed(size: usize, nan_density: f32, seed: u64) -> Float64Array {
906    let mut rng = StdRng::seed_from_u64(seed);
907
908    (0..size)
909        .map(|_| {
910            if rng.random::<f32>() < nan_density {
911                Some(f64::NAN)
912            } else {
913                Some(rng.random())
914            }
915        })
916        .collect()
917}
918
919/// Create a FixedSizeList array of primitive values
920///
921/// Arguments:
922/// - `size`: number of fixed-size lists in the array
923/// - `null_density`: density of nulls in the fixed-size list array (row-level nulls)
924/// - `value_null_density`: density of nulls in the primitive values inside each list
925/// - `list_size`: fixed size of each list element
926pub fn create_primitive_fixed_size_list_array<T>(
927    size: usize,
928    null_density: f32,
929    value_null_density: f32,
930    list_size: i32,
931) -> FixedSizeListArray
932where
933    T: ArrowPrimitiveType,
934    StandardUniform: Distribution<T::Native>,
935{
936    let mut rng = seedable_rng();
937    let list_size_usize = usize::try_from(list_size).expect("list_size must be non-negative");
938    let values: PrimitiveArray<T> = (0..size * list_size_usize)
939        .map(|_| {
940            if rng.random::<f32>() < value_null_density {
941                None
942            } else {
943                Some(rng.random())
944            }
945        })
946        .collect();
947    let field = Arc::new(Field::new("item", T::DATA_TYPE, value_null_density > 0.0));
948    let nulls = (null_density > 0.0).then(|| {
949        NullBuffer::new(arrow_buffer::BooleanBuffer::collect_bool(size, |_| {
950            rng.random::<f32>() >= null_density
951        }))
952    });
953    FixedSizeListArray::new(field, list_size, Arc::new(values), nulls)
954}
955
956/// Create a Map array with string keys and primitive values
957///
958/// Arguments:
959/// - `size`: number of map entries in the array
960/// - `null_density`: density of nulls in the map array (row-level nulls)
961/// - `max_map_size`: maximum number of key-value pairs per map entry
962///   (actual size is random between 0 and max_map_size)
963/// - `key_len`: length of each random string key
964pub fn create_string_map_array<T>(
965    size: usize,
966    null_density: f32,
967    max_map_size: usize,
968    key_len: usize,
969) -> MapArray
970where
971    T: ArrowPrimitiveType,
972    StandardUniform: Distribution<T::Native>,
973{
974    let mut rng = seedable_rng();
975    let mut builder = MapBuilder::new(None, StringBuilder::new(), PrimitiveBuilder::<T>::new());
976    for _ in 0..size {
977        if rng.random::<f32>() < null_density {
978            builder.append(false).unwrap();
979        } else {
980            let n = rng.random_range(0..=max_map_size);
981            for _ in 0..n {
982                builder
983                    .keys()
984                    .append_value(Alphanumeric.sample_string(&mut rng, key_len));
985                builder.values().append_value(rng.random());
986            }
987            builder.append(true).unwrap();
988        }
989    }
990    builder.finish()
991}
992
993/// Creates a random array for the given [`DataType`], `size`, and `null_density`.
994///
995/// Useful for building arrays and record batches in benchmarks without
996/// repeating per-type construction logic. Panics on unsupported types.
997///
998/// # Panics
999///
1000/// Panics if `data_type` is not supported
1001pub fn create_array_for_type(data_type: &DataType, size: usize, null_density: f32) -> ArrayRef {
1002    match data_type {
1003        DataType::Boolean => Arc::new(create_boolean_array(size, null_density, 0.5)),
1004        DataType::Int8 => Arc::new(create_primitive_array::<Int8Type>(size, null_density)),
1005        DataType::Int16 => Arc::new(create_primitive_array::<Int16Type>(size, null_density)),
1006        DataType::Int32 => Arc::new(create_primitive_array::<Int32Type>(size, null_density)),
1007        DataType::Int64 => Arc::new(create_primitive_array::<Int64Type>(size, null_density)),
1008        DataType::UInt8 => Arc::new(create_primitive_array::<UInt8Type>(size, null_density)),
1009        DataType::UInt16 => Arc::new(create_primitive_array::<UInt16Type>(size, null_density)),
1010        DataType::UInt32 => Arc::new(create_primitive_array::<UInt32Type>(size, null_density)),
1011        DataType::UInt64 => Arc::new(create_primitive_array::<UInt64Type>(size, null_density)),
1012        DataType::Float32 => Arc::new(create_primitive_array::<Float32Type>(size, null_density)),
1013        DataType::Float64 => Arc::new(create_primitive_array::<Float64Type>(size, null_density)),
1014        DataType::Utf8 => Arc::new(create_string_array::<i32>(size, null_density)),
1015        DataType::LargeUtf8 => Arc::new(create_string_array::<i64>(size, null_density)),
1016        DataType::Utf8View => Arc::new(create_string_view_array(size, null_density)),
1017        DataType::Binary => Arc::new(create_binary_array::<i32>(size, null_density)),
1018        DataType::LargeBinary => Arc::new(create_binary_array::<i64>(size, null_density)),
1019        DataType::FixedSizeBinary(n) => Arc::new(create_fsb_array(size, null_density, *n as usize)),
1020        other => panic!("unsupported data type for create_array_for_type: {other}"),
1021    }
1022}