Skip to main content

arrow_string/
substring.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//! Defines kernel to extract a substring of an Array
19//! Supported array types:
20//! [GenericStringArray], [GenericBinaryArray], [GenericByteViewArray],
21//! [FixedSizeBinaryArray], [DictionaryArray]
22
23use arrow_array::builder::{BinaryViewBuilder, StringViewBuilder};
24use arrow_array::cast::AsArray;
25use arrow_array::types::*;
26use arrow_array::*;
27use arrow_buffer::{ArrowNativeType, MutableBuffer, NullBuffer, OffsetBuffer};
28use arrow_schema::{ArrowError, DataType};
29use num_traits::{CheckedAdd, Zero};
30use std::cmp::Ordering;
31use std::sync::Arc;
32
33/// Returns an [`ArrayRef`] with substrings of all the elements in `array`.
34///
35/// # Arguments
36///
37/// * `start` - The start index of all substrings.
38///   If `start >= 0`, then count from the start of the string,
39///   otherwise count from the end of the string.
40///
41/// * `length`(option) - The length of all substrings.
42///   If `length` is [None], then the substring is from `start` to the end of the string.
43///
44/// Attention: Both `start` and `length` are counted by byte, not by char.
45///
46/// # Basic usage
47/// ```
48/// # use arrow_array::StringArray;
49/// # use arrow_string::substring::substring;
50/// let array = StringArray::from(vec![Some("arrow"), None, Some("rust")]);
51/// let result = substring(&array, 1, Some(4)).unwrap();
52/// let result = result.as_any().downcast_ref::<StringArray>().unwrap();
53/// assert_eq!(result, &StringArray::from(vec![Some("rrow"), None, Some("ust")]));
54/// ```
55///
56/// # Error
57/// - The function errors when the passed array is not a [`GenericStringArray`],
58///   [`GenericBinaryArray`], [`FixedSizeBinaryArray`] or [`DictionaryArray`]
59///   with supported array type as its value type.
60/// - The function errors if the offset of a substring in the input array is
61///   at invalid char boundary (only for \[Large\]String array).
62///   It is recommended to use [`substring_by_char`] if the input array may
63///   contain non-ASCII chars.
64///
65/// ## Example of trying to get an invalid utf-8 format substring
66/// ```
67/// # use arrow_array::StringArray;
68/// # use arrow_string::substring::substring;
69/// let array = StringArray::from(vec![Some("E=mc²")]);
70/// let error = substring(&array, 0, Some(5)).unwrap_err().to_string();
71/// assert!(error.contains("invalid utf-8 boundary"));
72/// ```
73pub fn substring(
74    array: &dyn Array,
75    start: i64,
76    length: Option<u64>,
77) -> Result<ArrayRef, ArrowError> {
78    match array.data_type() {
79        DataType::Dictionary(_, _) => {
80            let dictionary = array.as_any_dictionary();
81            let values = substring(dictionary.values(), start, length)?;
82            Ok(Arc::new(dictionary.with_values(values)))
83        }
84        DataType::LargeBinary => byte_substring(
85            array.as_binary::<i64>(),
86            start,
87            // ensure we saturate to not wrap around to a negative length
88            length.map(u64_to_i64_saturating),
89        ),
90        DataType::Binary => byte_substring(
91            array.as_binary::<i32>(),
92            // ensure to saturate to avoid wrapping to negative which is a different behaviour
93            i64_to_i32_saturating(start),
94            length.map(u64_to_i32_saturating),
95        ),
96        DataType::FixedSizeBinary(old_len) => {
97            let old_len: usize = (*old_len)
98                .try_into()
99                .expect("negative FixedSizeBinary value length");
100            fixed_size_binary_substring(array.as_fixed_size_binary(), old_len, start, length)
101        }
102        DataType::LargeUtf8 => byte_substring(
103            array.as_string::<i64>(),
104            start,
105            // ensure we saturate to not wrap around to a negative length
106            length.map(u64_to_i64_saturating),
107        ),
108        DataType::Utf8 => byte_substring(
109            array.as_string::<i32>(),
110            // ensure to saturate to avoid wrapping to negative which is a different behaviour
111            i64_to_i32_saturating(start),
112            length.map(u64_to_i32_saturating),
113        ),
114        DataType::Utf8View => string_view_substring(array.as_string_view(), start, length),
115        DataType::BinaryView => binary_view_substring(array.as_binary_view(), start, length),
116        _ => Err(ArrowError::ComputeError(format!(
117            "substring does not support type {:?}",
118            array.data_type()
119        ))),
120    }
121}
122
123/// Substrings based on character index
124///
125/// # Arguments
126/// * `array` - The input string array
127///
128/// * `start` - The start index of all substrings.
129///   If `start >= 0`, then count from the start of the string,
130///   otherwise count from the end of the string.
131///
132/// * `length`(option) - The length of all substrings.
133///   If `length` is `None`, then the substring is from `start` to the end of the string.
134///
135/// Attention: Both `start` and `length` are counted by char.
136///
137/// # Performance
138///
139/// This function is slower than [substring]. Theoretically, the time complexity
140/// is `O(n)` where `n` is the length of the value buffer. If the array only
141/// contains ASCII chars, a fast path avoids decoding UTF-8 altogether and the
142/// performance is comparable to [substring].
143///
144/// # Basic usage
145/// ```
146/// # use arrow_array::StringArray;
147/// # use arrow_string::substring::substring_by_char;
148/// let array = StringArray::from(vec![Some("arrow"), None, Some("Γ ⊢x:T")]);
149/// let result = substring_by_char(&array, 1, Some(4)).unwrap();
150/// assert_eq!(result, StringArray::from(vec![Some("rrow"), None, Some(" ⊢x:")]));
151/// ```
152pub fn substring_by_char<OffsetSize: OffsetSizeTrait>(
153    array: &GenericStringArray<OffsetSize>,
154    start: i64,
155    length: Option<u64>,
156) -> Result<GenericStringArray<OffsetSize>, ArrowError> {
157    let length = length.map(|len| usize::try_from(len).unwrap_or(usize::MAX));
158
159    if array.is_ascii() {
160        // One char is one byte, so the byte offsets can be computed arithmetically.
161        Ok(substring_by_char_impl(array, length, |val| {
162            ascii_bounds(val, start, length)
163        }))
164    } else {
165        // A char occupies at most 4 bytes.
166        let max_element_len = length.map(|len| len.saturating_mul(4));
167        Ok(substring_by_char_impl(array, max_element_len, |val| {
168            utf8_bounds(val, start, length)
169        }))
170    }
171}
172
173/// Builds the output of [`substring_by_char`], delegating to `bounds` to locate the
174/// substring of each value.
175///
176/// * `max_element_len` - an upper bound, in bytes, on the size of a single output
177///   element, used to avoid over-allocating the value buffer. [None] if unbounded.
178fn substring_by_char_impl<OffsetSize: OffsetSizeTrait, F: Fn(&str) -> (usize, usize)>(
179    array: &GenericStringArray<OffsetSize>,
180    max_element_len: Option<usize>,
181    bounds: F,
182) -> GenericStringArray<OffsetSize> {
183    let mut vals = Vec::with_capacity({
184        let offsets = array.value_offsets();
185        let input_len = (offsets[array.len()] - offsets[0]).to_usize().unwrap();
186        match max_element_len {
187            Some(len) => input_len.min(array.len().saturating_mul(len)),
188            None => input_len,
189        }
190    });
191    let mut new_offsets = Vec::with_capacity(array.len() + 1);
192    new_offsets.push(OffsetSize::zero());
193
194    array.iter().for_each(|val| {
195        if let Some(val) = val {
196            let (start_offset, end_offset) = bounds(val);
197            vals.extend_from_slice(&val.as_bytes()[start_offset..end_offset]);
198        }
199        new_offsets.push(OffsetSize::from_usize(vals.len()).unwrap());
200    });
201
202    let offsets = OffsetBuffer::new(new_offsets.into());
203    let values = vals.into();
204    let nulls = array
205        .nulls()
206        .map(|n| n.inner().sliced())
207        .and_then(|b| NullBuffer::from_unsliced_buffer(b, array.len()));
208    GenericStringArray::<OffsetSize>::new(offsets, values, nulls)
209}
210
211/// Returns the `start` and `end` byte offset of the substring of an ASCII `val`, where
212/// one char is one byte.
213///
214/// * `start` - the start char index of the substring, counted from the end if negative
215/// * `length` - the char length of the substring, or [None] to take the rest of `val`
216#[inline]
217fn ascii_bounds(val: &str, start: i64, length: Option<usize>) -> (usize, usize) {
218    let len = val.len();
219    let start_offset = if start >= 0 {
220        usize::try_from(start).unwrap_or(usize::MAX).min(len)
221    } else {
222        len.saturating_sub(usize::try_from(start.unsigned_abs()).unwrap_or(usize::MAX))
223    };
224    let end_offset = length.map_or(len, |length| start_offset.saturating_add(length).min(len));
225    (start_offset, end_offset)
226}
227
228/// Returns the `start` and `end` byte offset of the substring of an arbitrary UTF-8 `val`.
229///
230/// * `start` - the start char index of the substring, counted from the end if negative
231/// * `length` - the char length of the substring, or [None] to take the rest of `val`
232#[inline]
233fn utf8_bounds(val: &str, start: i64, length: Option<usize>) -> (usize, usize) {
234    let len = val.len();
235    let start_offset = if start >= 0 {
236        val.char_indices()
237            .nth(usize::try_from(start).unwrap_or(usize::MAX))
238            .map_or(len, |(offset, _)| offset)
239    } else {
240        // `start` is negative, so `nth_back` counts back from the last char. Strings with
241        // fewer than `-start` chars start at 0.
242        let back = usize::try_from(start.unsigned_abs()).unwrap_or(usize::MAX);
243        val.char_indices()
244            .nth_back(back - 1)
245            .map_or(0, |(offset, _)| offset)
246    };
247    let end_offset = length.map_or(len, |length| {
248        // Every char is at least one byte, so the rest of `val` is shorter than `length`
249        // chars and there is nothing to scan for.
250        if length >= len - start_offset {
251            return len;
252        }
253        val[start_offset..]
254            .char_indices()
255            .nth(length)
256            .map_or(len, |(offset, _)| start_offset + offset)
257    });
258    (start_offset, end_offset)
259}
260
261/// Byte range of one element, following the same rules as [`byte_substring`].
262fn view_substring_range(
263    original_length: usize,
264    start: i64,
265    substring_length: Option<u64>,
266) -> (usize, usize) {
267    let original_length = original_length as i64;
268    let new_start = match start.cmp(&0) {
269        Ordering::Greater => start.min(original_length),
270        Ordering::Equal => 0,
271        Ordering::Less => (original_length + start).max(0),
272    };
273    let new_end = match substring_length {
274        Some(length) => new_start.saturating_add(length as i64).min(original_length),
275        None => original_length,
276    };
277    (new_start as usize, new_end as usize)
278}
279
280fn string_view_substring(
281    array: &StringViewArray,
282    start: i64,
283    length: Option<u64>,
284) -> Result<ArrayRef, ArrowError> {
285    let mut builder = StringViewBuilder::with_capacity(array.len());
286
287    for idx in 0..array.len() {
288        if array.is_null(idx) {
289            builder.append_null();
290            continue;
291        }
292        let value = array.value(idx);
293        let (new_start, new_end) = view_substring_range(value.len(), start, length);
294        for offset in [new_start, new_end] {
295            if !value.is_char_boundary(offset) {
296                return Err(ArrowError::ComputeError(format!(
297                    "The offset {offset} is at an invalid utf-8 boundary."
298                )));
299            }
300        }
301        builder.append_value(&value[new_start..new_end]);
302    }
303
304    Ok(Arc::new(builder.finish()))
305}
306
307fn binary_view_substring(
308    array: &BinaryViewArray,
309    start: i64,
310    length: Option<u64>,
311) -> Result<ArrayRef, ArrowError> {
312    let mut builder = BinaryViewBuilder::with_capacity(array.len());
313
314    for idx in 0..array.len() {
315        if array.is_null(idx) {
316            builder.append_null();
317            continue;
318        }
319        let value = array.value(idx);
320        let (new_start, new_end) = view_substring_range(value.len(), start, length);
321        builder.append_value(&value[new_start..new_end]);
322    }
323
324    Ok(Arc::new(builder.finish()))
325}
326
327fn i64_to_i32_saturating(value: i64) -> i32 {
328    value.clamp(i32::MIN as i64, i32::MAX as i64) as i32
329}
330
331fn u64_to_i32_saturating(value: u64) -> i32 {
332    value.min(i32::MAX as u64) as i32
333}
334
335fn u64_to_i64_saturating(value: u64) -> i64 {
336    value.min(i64::MAX as u64) as i64
337}
338
339fn byte_substring<T: ByteArrayType>(
340    array: &GenericByteArray<T>,
341    start: T::Offset,
342    length: Option<T::Offset>,
343) -> Result<ArrayRef, ArrowError>
344where
345    <T as ByteArrayType>::Native: PartialEq,
346{
347    let offsets = array.value_offsets();
348    let data = array.value_data();
349    let zero = <T::Offset as Zero>::zero();
350
351    // When array is [Large]StringArray, we will check whether `offset` is at a valid char boundary.
352    let check_char_boundary = {
353        let first = array.offsets().first().as_usize();
354        let last = array.offsets().last().as_usize();
355        move |offset: T::Offset| {
356            if !matches!(T::DATA_TYPE, DataType::Utf8 | DataType::LargeUtf8) {
357                return Ok(offset);
358            }
359            // Safety: in a StringArray, the bytes between the first and last offsets
360            // are valid UTF-8. The bytes outside that range need not be.
361            let data_str = unsafe { std::str::from_utf8_unchecked(&data[first..last]) };
362            let offset_usize = offset.as_usize();
363            if data_str.is_char_boundary(offset_usize - first) {
364                Ok(offset)
365            } else {
366                Err(ArrowError::ComputeError(format!(
367                    "The offset {offset_usize} is at an invalid utf-8 boundary."
368                )))
369            }
370        }
371    };
372
373    // start and end offsets of all substrings
374    let mut new_starts_ends: Vec<(T::Offset, T::Offset)> = Vec::with_capacity(array.len());
375    let mut new_offsets: Vec<T::Offset> = Vec::with_capacity(array.len() + 1);
376    let mut len_so_far = zero;
377    new_offsets.push(zero);
378
379    offsets
380        .windows(2)
381        .try_for_each(|pair| -> Result<(), ArrowError> {
382            let new_start = match start.cmp(&zero) {
383                Ordering::Greater => {
384                    // a saturated start can carry pair[0] + start past the offset
385                    // type. that means past the end of this value, so clamp to the
386                    // end rather than let the add wrap.
387                    let shifted = pair[0].checked_add(&start).unwrap_or(pair[1]);
388                    check_char_boundary(shifted.min(pair[1]))?
389                }
390                Ordering::Equal => pair[0],
391                Ordering::Less => check_char_boundary((pair[1] + start).max(pair[0]))?,
392            };
393            let new_end = match length {
394                Some(length) => {
395                    let end = length.checked_add(&new_start).unwrap_or(pair[1]);
396                    check_char_boundary(end.min(pair[1]))?
397                }
398                None => pair[1],
399            };
400            len_so_far += new_end - new_start;
401            new_starts_ends.push((new_start, new_end));
402            new_offsets.push(len_so_far);
403            Ok(())
404        })?;
405
406    // concatenate substrings into a buffer
407    let mut new_values = MutableBuffer::new(new_offsets.last().unwrap().as_usize());
408
409    new_starts_ends
410        .iter()
411        .map(|(start, end)| {
412            let start = start.as_usize();
413            let end = end.as_usize();
414            &data[start..end]
415        })
416        .try_for_each(|slice| {
417            new_values
418                .try_extend_from_slice(slice)
419                .map_err(|e| ArrowError::MemoryError(e.to_string()))
420        })?;
421
422    let offsets = OffsetBuffer::new(new_offsets.into());
423    let values = new_values.into();
424    let nulls = array
425        .nulls()
426        .map(|n| n.inner().sliced())
427        .and_then(|b| NullBuffer::from_unsliced_buffer(b, array.len()));
428    Ok(Arc::new(GenericByteArray::<T>::new(offsets, values, nulls)))
429}
430
431fn fixed_size_binary_substring(
432    array: &FixedSizeBinaryArray,
433    old_len: usize,
434    start: i64,
435    length: Option<u64>,
436) -> Result<ArrayRef, ArrowError> {
437    let new_start = match start.cmp(&0) {
438        Ordering::Greater => usize::try_from(start).unwrap_or(usize::MAX).min(old_len),
439        Ordering::Equal => 0,
440        Ordering::Less => {
441            let offset = usize::try_from(start.unsigned_abs()).unwrap_or(usize::MAX);
442            old_len.saturating_sub(offset)
443        }
444    };
445
446    let new_len = match length {
447        Some(len) => usize::try_from(len)
448            .unwrap_or(usize::MAX)
449            .min(old_len - new_start),
450        None => old_len - new_start,
451    };
452
453    // build value buffer
454    let num_of_elements = array.len();
455    let data = array.value_data();
456    let capacity = num_of_elements
457        .checked_mul(new_len)
458        .expect("capacity overflow");
459    let mut new_values = MutableBuffer::new(capacity);
460    (0..num_of_elements)
461        .map(|idx| {
462            let offset = idx * array.value_size();
463            (offset + new_start, offset + new_start + new_len)
464        })
465        .try_for_each(|(start, end)| {
466            new_values
467                .try_extend_from_slice(&data[start..end])
468                .map_err(|e| ArrowError::MemoryError(e.to_string()))
469        })?;
470
471    let mut nulls = array
472        .nulls()
473        .map(|n| n.inner().sliced())
474        .and_then(|b| NullBuffer::from_unsliced_buffer(b, num_of_elements));
475
476    if new_len == 0 && nulls.is_none() {
477        // FixedSizeBinaryArray::new takes length from the values buffer, except when size == 0.
478        // In that case it uses the null buffer length, so preserve the original length here.
479        // Example: ["", "", ""] -> substring(..., 1, Some(2)) should keep len=3;
480        // otherwise it collapses to an empty array (len=0).
481        nulls = Some(NullBuffer::new_valid(num_of_elements));
482    }
483
484    let new_len: i32 = new_len.try_into().expect("new_len overflow");
485
486    Ok(Arc::new(FixedSizeBinaryArray::new(
487        new_len,
488        new_values.into(),
489        nulls,
490    )))
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496    use arrow_buffer::BooleanBuffer;
497    use arrow_buffer::Buffer;
498
499    /// A helper macro to generate test cases.
500    /// # Arguments
501    /// * `input` - A vector which array can be built from.
502    /// * `start` - The start index of the substring.
503    /// * `len` - The length of the substring.
504    /// * `result` - The expected result of substring, which is a vector that array can be built from.
505    /// # Return
506    /// A vector of `(input, start, len, result)`.
507    ///
508    /// Users can provide any number of `(start, len, result)` to generate test cases for one `input`.
509    macro_rules! gen_test_cases {
510        ($input:expr, $(($start:expr, $len:expr, $result:expr)), *) => {
511            [
512                $(
513                    ($input.clone(), $start, $len, $result),
514                )*
515            ]
516        };
517    }
518
519    /// A helper macro to test the substring functions.
520    /// # Arguments
521    /// * `cases` - The test cases which is a vector of `(input, start, len, result)`.
522    ///   Please look at [`gen_test_cases`] to find how to generate it.
523    /// * `array_ty` - The array type.
524    /// * `substring_fn` - Either [`substring`] or [`substring_by_char`].
525    macro_rules! do_test {
526        ($cases:expr, $array_ty:ty, $substring_fn:ident) => {
527            $cases
528                .into_iter()
529                .for_each(|(array, start, length, expected)| {
530                    let array = <$array_ty>::from(array);
531                    let result = $substring_fn(&array, start, length).unwrap();
532                    let result = result.as_any().downcast_ref::<$array_ty>().unwrap();
533                    let expected = <$array_ty>::from(expected);
534                    assert_eq!(&expected, result);
535                })
536        };
537    }
538
539    /// A helper macro to test the substring functions for array types only implementing TryFrom.
540    macro_rules! do_test_tryfrom {
541        ($cases:expr, $array_ty:ty, $substring_fn:ident) => {
542            $cases
543                .into_iter()
544                .for_each(|(array, start, length, expected)| {
545                    let array = <$array_ty>::try_from(array).unwrap();
546                    let result = $substring_fn(&array, start, length).unwrap();
547                    let result = result.as_any().downcast_ref::<$array_ty>().unwrap();
548                    let expected = <$array_ty>::try_from(expected).unwrap();
549                    assert_eq!(&expected, result);
550                })
551        };
552    }
553
554    fn with_nulls_generic_binary<O: OffsetSizeTrait>() {
555        let input = vec![
556            Some(b"hello".as_slice()),
557            None,
558            Some(&[0xf8, 0xf9, 0xff, 0xfa]),
559        ];
560        // all-nulls array is always identical
561        let base_case = gen_test_cases!(
562            vec![None, None, None],
563            (-1, Some(1), vec![None, None, None])
564        );
565        let cases = gen_test_cases!(
566            input,
567            // identity
568            (0, None, input.clone()),
569            // 0 length -> Nothing
570            (0, Some(0), vec![Some(&[]), None, Some(&[])]),
571            // high start -> Nothing
572            (1000, Some(0), vec![Some(&[]), None, Some(&[])]),
573            // high negative start -> identity
574            (-1000, None, input.clone()),
575            // high length -> identity
576            (0, Some(1000), input.clone())
577        );
578
579        do_test!(
580            [&base_case[..], &cases[..]].concat(),
581            GenericBinaryArray<O>,
582            substring
583        );
584    }
585
586    #[test]
587    fn with_nulls_binary() {
588        with_nulls_generic_binary::<i32>()
589    }
590
591    #[test]
592    fn with_nulls_large_binary() {
593        with_nulls_generic_binary::<i64>()
594    }
595
596    fn without_nulls_generic_binary<O: OffsetSizeTrait>() {
597        let input = vec![b"hello".as_slice(), b"", &[0xf8, 0xf9, 0xff, 0xfa]];
598        // empty array is always identical
599        let base_case = gen_test_cases!(
600            vec![b"".as_slice(), b"", b""],
601            (2, Some(1), vec![b"".as_slice(), b"", b""])
602        );
603        let cases = gen_test_cases!(
604            input,
605            // identity
606            (0, None, input.clone()),
607            // increase start
608            (1, None, vec![b"ello", b"", &[0xf9, 0xff, 0xfa]]),
609            (2, None, vec![b"llo", b"", &[0xff, 0xfa]]),
610            (3, None, vec![b"lo", b"", &[0xfa]]),
611            (10, None, vec![b"", b"", b""]),
612            // increase start negatively
613            (-1, None, vec![b"o", b"", &[0xfa]]),
614            (-2, None, vec![b"lo", b"", &[0xff, 0xfa]]),
615            (-3, None, vec![b"llo", b"", &[0xf9, 0xff, 0xfa]]),
616            (-10, None, input.clone()),
617            // increase length
618            (1, Some(1), vec![b"e", b"", &[0xf9]]),
619            (1, Some(2), vec![b"el", b"", &[0xf9, 0xff]]),
620            (1, Some(3), vec![b"ell", b"", &[0xf9, 0xff, 0xfa]]),
621            (1, Some(4), vec![b"ello", b"", &[0xf9, 0xff, 0xfa]]),
622            (-3, Some(1), vec![b"l", b"", &[0xf9]]),
623            (-3, Some(2), vec![b"ll", b"", &[0xf9, 0xff]]),
624            (-3, Some(3), vec![b"llo", b"", &[0xf9, 0xff, 0xfa]]),
625            (-3, Some(4), vec![b"llo", b"", &[0xf9, 0xff, 0xfa]])
626        );
627
628        do_test!(
629            [&base_case[..], &cases[..]].concat(),
630            GenericBinaryArray<O>,
631            substring
632        );
633    }
634
635    #[test]
636    fn without_nulls_binary() {
637        without_nulls_generic_binary::<i32>()
638    }
639
640    #[test]
641    fn without_nulls_large_binary() {
642        without_nulls_generic_binary::<i64>()
643    }
644
645    fn generic_binary_with_non_zero_offset<O: OffsetSizeTrait>() {
646        let values = 0_u8..15;
647        let offsets = &[
648            O::zero(),
649            O::from_usize(5).unwrap(),
650            O::from_usize(10).unwrap(),
651            O::from_usize(15).unwrap(),
652        ];
653        // set the first and third element to be valid
654        let bitmap = [0b101_u8];
655
656        let offsets = OffsetBuffer::new(Buffer::from_slice_ref(offsets).into());
657        let values = Buffer::from_iter(values);
658        let nulls = Some(NullBuffer::new(BooleanBuffer::new(
659            Buffer::from(bitmap),
660            0,
661            3,
662        )));
663        // array is `[null, [10, 11, 12, 13, 14]]`
664        let array = GenericBinaryArray::<O>::new(offsets, values, nulls).slice(1, 2);
665        // result is `[null, [11, 12, 13, 14]]`
666        let result = substring(&array, 1, None).unwrap();
667        let result = result
668            .as_any()
669            .downcast_ref::<GenericBinaryArray<O>>()
670            .unwrap();
671        let expected =
672            GenericBinaryArray::<O>::from_opt_vec(vec![None, Some(&[11_u8, 12, 13, 14])]);
673        assert_eq!(result, &expected);
674    }
675
676    #[test]
677    fn binary_with_non_zero_offset() {
678        generic_binary_with_non_zero_offset::<i32>()
679    }
680
681    #[test]
682    fn large_binary_with_non_zero_offset() {
683        generic_binary_with_non_zero_offset::<i64>()
684    }
685
686    #[test]
687    fn with_nulls_fixed_size_binary() {
688        let input = vec![Some(b"cat".as_slice()), None, Some(&[0xf8, 0xf9, 0xff])];
689        // all-nulls array is always identical
690        let base_case =
691            gen_test_cases!(vec![None, None, None], (3, Some(2), vec![None, None, None]));
692        let cases = gen_test_cases!(
693            input,
694            // identity
695            (0, None, input.clone()),
696            // increase start
697            (1, None, vec![Some(b"at"), None, Some(&[0xf9, 0xff])]),
698            (2, None, vec![Some(b"t"), None, Some(&[0xff])]),
699            (3, None, vec![Some(b""), None, Some(b"")]),
700            (10, None, vec![Some(b""), None, Some(b"")]),
701            // increase start negatively
702            (-1, None, vec![Some(b"t"), None, Some(&[0xff])]),
703            (-2, None, vec![Some(b"at"), None, Some(&[0xf9, 0xff])]),
704            (-3, None, input.clone()),
705            (-10, None, input.clone()),
706            // increase length
707            (1, Some(1), vec![Some(b"a"), None, Some(&[0xf9])]),
708            (1, Some(2), vec![Some(b"at"), None, Some(&[0xf9, 0xff])]),
709            (1, Some(3), vec![Some(b"at"), None, Some(&[0xf9, 0xff])]),
710            (-3, Some(1), vec![Some(b"c"), None, Some(&[0xf8])]),
711            (-3, Some(2), vec![Some(b"ca"), None, Some(&[0xf8, 0xf9])]),
712            (-3, Some(3), input.clone()),
713            (-3, Some(4), input.clone())
714        );
715
716        do_test_tryfrom!(
717            [&base_case[..], &cases[..]].concat(),
718            FixedSizeBinaryArray,
719            substring
720        );
721    }
722
723    #[test]
724    fn without_nulls_fixed_size_binary() {
725        let input = vec![b"cat".as_slice(), b"dog", &[0xf8, 0xf9, 0xff]];
726        // empty array is always identical
727        let base_case = gen_test_cases!(
728            vec![b"".as_slice(), &[], &[]],
729            (1, Some(2), vec![b"".as_slice(), &[], &[]])
730        );
731        let cases = gen_test_cases!(
732            input,
733            // identity
734            (0, None, input.clone()),
735            // increase start
736            (1, None, vec![b"at", b"og", &[0xf9, 0xff]]),
737            (2, None, vec![b"t", b"g", &[0xff]]),
738            (3, None, vec![&[], &[], &[]]),
739            (10, None, vec![&[], &[], &[]]),
740            // increase start negatively
741            (-1, None, vec![b"t", b"g", &[0xff]]),
742            (-2, None, vec![b"at", b"og", &[0xf9, 0xff]]),
743            (-3, None, input.clone()),
744            (-10, None, input.clone()),
745            // increase length
746            (1, Some(1), vec![b"a", b"o", &[0xf9]]),
747            (1, Some(2), vec![b"at", b"og", &[0xf9, 0xff]]),
748            (1, Some(3), vec![b"at", b"og", &[0xf9, 0xff]]),
749            (-3, Some(1), vec![b"c", b"d", &[0xf8]]),
750            (-3, Some(2), vec![b"ca", b"do", &[0xf8, 0xf9]]),
751            (-3, Some(3), input.clone()),
752            (-3, Some(4), input.clone())
753        );
754
755        do_test_tryfrom!(
756            [&base_case[..], &cases[..]].concat(),
757            FixedSizeBinaryArray,
758            substring
759        );
760    }
761
762    #[test]
763    fn fixed_size_binary_with_non_zero_offset() {
764        let values = b"hellotherearrow";
765        // set the first and third element to be valid
766        let bits_v = [0b101_u8];
767
768        let nulls = Some(NullBuffer::new(BooleanBuffer::new(
769            Buffer::from(bits_v),
770            0,
771            3,
772        )));
773        // array is `[null, "arrow"]`
774        let array = FixedSizeBinaryArray::new(5, Buffer::from(values), nulls).slice(1, 2);
775        // result is `[null, "rrow"]`
776        let result = substring(&array, 1, None).unwrap();
777        let result = result
778            .as_any()
779            .downcast_ref::<FixedSizeBinaryArray>()
780            .unwrap();
781        let expected = FixedSizeBinaryArray::try_from_sparse_iter_with_size(
782            vec![None, Some(b"rrow")].into_iter(),
783            4,
784        )
785        .unwrap();
786        assert_eq!(result, &expected);
787    }
788
789    fn with_nulls_generic_string<O: OffsetSizeTrait>() {
790        let input = vec![Some("hello"), None, Some("word")];
791        // all-nulls array is always identical
792        let base_case = gen_test_cases!(vec![None, None, None], (0, None, vec![None, None, None]));
793        let cases = gen_test_cases!(
794            input,
795            // identity
796            (0, None, input.clone()),
797            // 0 length -> Nothing
798            (0, Some(0), vec![Some(""), None, Some("")]),
799            // high start -> Nothing
800            (1000, Some(0), vec![Some(""), None, Some("")]),
801            // high negative start -> identity
802            (-1000, None, input.clone()),
803            // high length -> identity
804            (0, Some(1000), input.clone())
805        );
806
807        do_test!(
808            [&base_case[..], &cases[..]].concat(),
809            GenericStringArray<O>,
810            substring
811        );
812    }
813
814    #[test]
815    fn with_nulls_string() {
816        with_nulls_generic_string::<i32>()
817    }
818
819    #[test]
820    fn with_nulls_large_string() {
821        with_nulls_generic_string::<i64>()
822    }
823
824    fn without_nulls_generic_string<O: OffsetSizeTrait>() {
825        let input = vec!["hello", "", "word"];
826        // empty array is always identical
827        let base_case = gen_test_cases!(vec!["", "", ""], (0, None, vec!["", "", ""]));
828        let cases = gen_test_cases!(
829            input,
830            // identity
831            (0, None, input.clone()),
832            (1, None, vec!["ello", "", "ord"]),
833            (2, None, vec!["llo", "", "rd"]),
834            (3, None, vec!["lo", "", "d"]),
835            (10, None, vec!["", "", ""]),
836            // increase start negatively
837            (-1, None, vec!["o", "", "d"]),
838            (-2, None, vec!["lo", "", "rd"]),
839            (-3, None, vec!["llo", "", "ord"]),
840            (-10, None, input.clone()),
841            // increase length
842            (1, Some(1), vec!["e", "", "o"]),
843            (1, Some(2), vec!["el", "", "or"]),
844            (1, Some(3), vec!["ell", "", "ord"]),
845            (1, Some(4), vec!["ello", "", "ord"]),
846            (-3, Some(1), vec!["l", "", "o"]),
847            (-3, Some(2), vec!["ll", "", "or"]),
848            (-3, Some(3), vec!["llo", "", "ord"]),
849            (-3, Some(4), vec!["llo", "", "ord"])
850        );
851
852        do_test!(
853            [&base_case[..], &cases[..]].concat(),
854            GenericStringArray<O>,
855            substring
856        );
857    }
858
859    #[test]
860    fn without_nulls_string() {
861        without_nulls_generic_string::<i32>()
862    }
863
864    #[test]
865    fn without_nulls_large_string() {
866        without_nulls_generic_string::<i64>()
867    }
868
869    fn generic_string_with_non_zero_offset<O: OffsetSizeTrait>() {
870        let values = b"hellotherearrow";
871        let offsets = &[
872            O::zero(),
873            O::from_usize(5).unwrap(),
874            O::from_usize(10).unwrap(),
875            O::from_usize(15).unwrap(),
876        ];
877        // set the first and third element to be valid
878        let bitmap = [0b101_u8];
879
880        let offsets = OffsetBuffer::new(Buffer::from_slice_ref(offsets).into());
881        let values = Buffer::from(values);
882        let nulls = Some(NullBuffer::new(BooleanBuffer::new(
883            Buffer::from(bitmap),
884            0,
885            3,
886        )));
887        // array is `[null, "arrow"]`
888        let array = GenericStringArray::<O>::new(offsets, values, nulls).slice(1, 2);
889        // result is `[null, "rrow"]`
890        let result = substring(&array, 1, None).unwrap();
891        let result = result
892            .as_any()
893            .downcast_ref::<GenericStringArray<O>>()
894            .unwrap();
895        let expected = GenericStringArray::<O>::from(vec![None, Some("rrow")]);
896        assert_eq!(result, &expected);
897    }
898
899    #[test]
900    fn string_with_non_zero_offset() {
901        generic_string_with_non_zero_offset::<i32>()
902    }
903
904    #[test]
905    fn large_string_with_non_zero_offset() {
906        generic_string_with_non_zero_offset::<i64>()
907    }
908
909    fn with_nulls_generic_string_by_char<O: OffsetSizeTrait>() {
910        let input = vec![Some("hello"), None, Some("Γ ⊢x:T")];
911        // all-nulls array is always identical
912        let base_case = gen_test_cases!(vec![None, None, None], (0, None, vec![None, None, None]));
913        let cases = gen_test_cases!(
914            input,
915            // identity
916            (0, None, input.clone()),
917            // 0 length -> Nothing
918            (0, Some(0), vec![Some(""), None, Some("")]),
919            // high start -> Nothing
920            (1000, Some(0), vec![Some(""), None, Some("")]),
921            // high negative start -> identity
922            (-1000, None, input.clone()),
923            // high length -> identity
924            (0, Some(1000), input.clone())
925        );
926
927        do_test!(
928            [&base_case[..], &cases[..]].concat(),
929            GenericStringArray<O>,
930            substring_by_char
931        );
932    }
933
934    #[test]
935    fn with_nulls_string_by_char() {
936        with_nulls_generic_string_by_char::<i32>()
937    }
938
939    #[test]
940    fn with_nulls_large_string_by_char() {
941        with_nulls_generic_string_by_char::<i64>()
942    }
943
944    fn without_nulls_generic_string_by_char<O: OffsetSizeTrait>() {
945        let input = vec!["hello", "", "Γ ⊢x:T"];
946        // empty array is always identical
947        let base_case = gen_test_cases!(vec!["", "", ""], (0, None, vec!["", "", ""]));
948        let cases = gen_test_cases!(
949            input,
950            //identity
951            (0, None, input.clone()),
952            // increase start
953            (1, None, vec!["ello", "", " ⊢x:T"]),
954            (2, None, vec!["llo", "", "⊢x:T"]),
955            (3, None, vec!["lo", "", "x:T"]),
956            (10, None, vec!["", "", ""]),
957            // increase start negatively
958            (-1, None, vec!["o", "", "T"]),
959            (-2, None, vec!["lo", "", ":T"]),
960            (-4, None, vec!["ello", "", "⊢x:T"]),
961            (-10, None, input.clone()),
962            // increase length
963            (1, Some(1), vec!["e", "", " "]),
964            (1, Some(2), vec!["el", "", " ⊢"]),
965            (1, Some(3), vec!["ell", "", " ⊢x"]),
966            (1, Some(6), vec!["ello", "", " ⊢x:T"]),
967            (-4, Some(1), vec!["e", "", "⊢"]),
968            (-4, Some(2), vec!["el", "", "⊢x"]),
969            (-4, Some(3), vec!["ell", "", "⊢x:"]),
970            (-4, Some(4), vec!["ello", "", "⊢x:T"])
971        );
972
973        do_test!(
974            [&base_case[..], &cases[..]].concat(),
975            GenericStringArray<O>,
976            substring_by_char
977        );
978    }
979
980    #[test]
981    fn without_nulls_string_by_char() {
982        without_nulls_generic_string_by_char::<i32>()
983    }
984
985    #[test]
986    fn without_nulls_large_string_by_char() {
987        without_nulls_generic_string_by_char::<i64>()
988    }
989
990    /// The array is all-ASCII, which takes the fast path of [`substring_by_char`].
991    fn ascii_generic_string_by_char<O: OffsetSizeTrait>() {
992        let input = vec![Some("hello"), None, Some(""), Some("rust")];
993        let cases = gen_test_cases!(
994            input,
995            // identity
996            (0, None, input.clone()),
997            // increase start
998            (1, None, vec![Some("ello"), None, Some(""), Some("ust")]),
999            (4, None, vec![Some("o"), None, Some(""), Some("")]),
1000            // high start -> nothing
1001            (1000, None, vec![Some(""), None, Some(""), Some("")]),
1002            // increase start negatively
1003            (-1, None, vec![Some("o"), None, Some(""), Some("t")]),
1004            (-4, None, vec![Some("ello"), None, Some(""), Some("rust")]),
1005            // high negative start -> identity
1006            (-1000, None, input.clone()),
1007            // 0 length -> nothing
1008            (0, Some(0), vec![Some(""), None, Some(""), Some("")]),
1009            // increase length
1010            (1, Some(2), vec![Some("el"), None, Some(""), Some("us")]),
1011            (-4, Some(2), vec![Some("el"), None, Some(""), Some("ru")]),
1012            // high length -> identity
1013            (0, Some(1000), input.clone()),
1014            // length past the end is clamped, including when it would overflow
1015            (
1016                1,
1017                Some(u64::MAX),
1018                vec![Some("ello"), None, Some(""), Some("ust")]
1019            )
1020        );
1021
1022        do_test!(cases, GenericStringArray<O>, substring_by_char);
1023    }
1024
1025    #[test]
1026    fn ascii_string_by_char() {
1027        ascii_generic_string_by_char::<i32>()
1028    }
1029
1030    #[test]
1031    fn ascii_large_string_by_char() {
1032        ascii_generic_string_by_char::<i64>()
1033    }
1034
1035    fn generic_string_by_char_with_non_zero_offset<O: OffsetSizeTrait>() {
1036        let values = "S→T = Πx:S.T";
1037        let offsets = &[
1038            O::zero(),
1039            O::from_usize(values.char_indices().nth(3).map(|(pos, _)| pos).unwrap()).unwrap(),
1040            O::from_usize(values.char_indices().nth(6).map(|(pos, _)| pos).unwrap()).unwrap(),
1041            O::from_usize(values.len()).unwrap(),
1042        ];
1043        // set the first and third element to be valid
1044        let bitmap = [0b101_u8];
1045
1046        let offsets = OffsetBuffer::new(Buffer::from_slice_ref(offsets).into());
1047        let values = Buffer::from(values.as_bytes());
1048        let nulls = Some(NullBuffer::new(BooleanBuffer::new(
1049            Buffer::from(bitmap),
1050            0,
1051            3,
1052        )));
1053        // array is `[null, "Πx:S.T"]`
1054        let array = GenericStringArray::<O>::new(offsets, values, nulls).slice(1, 2);
1055        // result is `[null, "x:S.T"]`
1056        let result = substring_by_char(&array, 1, None).unwrap();
1057        let expected = GenericStringArray::<O>::from(vec![None, Some("x:S.T")]);
1058        assert_eq!(result, expected);
1059    }
1060
1061    #[test]
1062    fn string_with_non_zero_offset_by_char() {
1063        generic_string_by_char_with_non_zero_offset::<i32>()
1064    }
1065
1066    #[test]
1067    fn large_string_with_non_zero_offset_by_char() {
1068        generic_string_by_char_with_non_zero_offset::<i64>()
1069    }
1070
1071    #[test]
1072    fn dictionary() {
1073        _dictionary::<Int8Type>();
1074        _dictionary::<Int16Type>();
1075        _dictionary::<Int32Type>();
1076        _dictionary::<Int64Type>();
1077        _dictionary::<UInt8Type>();
1078        _dictionary::<UInt16Type>();
1079        _dictionary::<UInt32Type>();
1080        _dictionary::<UInt64Type>();
1081    }
1082
1083    fn _dictionary<K: ArrowDictionaryKeyType>() {
1084        const TOTAL: i32 = 100;
1085
1086        let v = ["aaa", "bbb", "ccc", "ddd", "eee"];
1087        let data: Vec<Option<&str>> = (0..TOTAL)
1088            .map(|n| {
1089                let i = n % 5;
1090                if i == 3 { None } else { Some(v[i as usize]) }
1091            })
1092            .collect();
1093
1094        let dict_array: DictionaryArray<K> = data.clone().into_iter().collect();
1095
1096        let expected: Vec<Option<&str>> = data.iter().map(|opt| opt.map(|s| &s[1..3])).collect();
1097
1098        let res = substring(&dict_array, 1, Some(2)).unwrap();
1099        let actual = res.as_any().downcast_ref::<DictionaryArray<K>>().unwrap();
1100        let actual: Vec<Option<&str>> = actual
1101            .values()
1102            .as_any()
1103            .downcast_ref::<GenericStringArray<i32>>()
1104            .unwrap()
1105            .take_iter(actual.keys_iter())
1106            .collect();
1107
1108        for i in 0..TOTAL as usize {
1109            assert_eq!(expected[i], actual[i],);
1110        }
1111    }
1112
1113    #[test]
1114    fn check_invalid_array_type() {
1115        let array = Int32Array::from(vec![Some(1), Some(2), Some(3)]);
1116        let err = substring(&array, 0, None).unwrap_err().to_string();
1117        assert!(err.contains("substring does not support type"));
1118    }
1119
1120    // tests for the utf-8 validation checking
1121    #[test]
1122    fn check_start_index() {
1123        let array = StringArray::from(vec![Some("E=mc²"), Some("ascii")]);
1124        let err = substring(&array, -1, None).unwrap_err().to_string();
1125        assert!(err.contains("invalid utf-8 boundary"));
1126    }
1127
1128    #[test]
1129    fn check_length() {
1130        let array = StringArray::from(vec![Some("E=mc²"), Some("ascii")]);
1131        let err = substring(&array, 0, Some(5)).unwrap_err().to_string();
1132        assert!(err.contains("invalid utf-8 boundary"));
1133    }
1134
1135    #[test]
1136    fn check_invalid_bytes_outside_offsets() {
1137        // The values buffer holds invalid UTF-8 before and after the visible values
1138        let values = Buffer::from_slice_ref(b"\xFFab\x80");
1139        let array = StringArray::new(OffsetBuffer::new(vec![1, 3].into()), values, None);
1140        let result = substring(&array, 0, Some(5)).unwrap();
1141        assert_eq!(result.as_string::<i32>(), &StringArray::from(vec!["ab"]));
1142    }
1143
1144    #[test]
1145    fn non_utf8_bytes() {
1146        // non-utf8 bytes
1147        let bytes: &[u8] = &[0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD, 0xE8, 0xAF, 0xAD];
1148        let array = BinaryArray::from(vec![Some(bytes)]);
1149        let arr = substring(&array, 0, Some(5)).unwrap();
1150        let actual = arr.as_any().downcast_ref::<BinaryArray>().unwrap();
1151
1152        let expected_bytes: &[u8] = &[0xE4, 0xBD, 0xA0, 0xE5, 0xA5];
1153        let expected = BinaryArray::from(vec![Some(expected_bytes)]);
1154        assert_eq!(expected, *actual);
1155    }
1156
1157    #[test]
1158    fn string_view_matches_utf8() {
1159        let values = vec![
1160            Some("hello world"),
1161            Some(""),
1162            None,
1163            Some("a"),
1164            Some("this one is definitely longer than twelve bytes"),
1165        ];
1166        let utf8 = StringArray::from(values.clone());
1167        let view = StringViewArray::from(values);
1168
1169        for (start, length) in [
1170            (0, None),
1171            (0, Some(0)),
1172            (0, Some(5)),
1173            (0, Some(1000)),
1174            (1, Some(3)),
1175            (5, None),
1176            (100, Some(2)),
1177            (100, None),
1178            (-3, None),
1179            (-3, Some(2)),
1180            (-100, Some(4)),
1181            (-100, None),
1182        ] {
1183            let expected = substring(&utf8, start, length).unwrap();
1184            let expected = expected.as_string::<i32>();
1185            let actual = substring(&view, start, length).unwrap();
1186            let actual = actual.as_string_view();
1187            assert_eq!(
1188                expected.iter().collect::<Vec<_>>(),
1189                actual.iter().collect::<Vec<_>>(),
1190                "start={start} length={length:?}"
1191            );
1192        }
1193    }
1194
1195    #[test]
1196    fn binary_view_matches_binary() {
1197        let values: Vec<Option<&[u8]>> = vec![
1198            Some(b"hello world"),
1199            Some(b""),
1200            None,
1201            Some(b"abc"),
1202            Some(b"this one is definitely longer than twelve bytes"),
1203        ];
1204        let binary = BinaryArray::from(values.clone());
1205        let view = BinaryViewArray::from(values);
1206
1207        for (start, length) in [
1208            (0, None),
1209            (0, Some(5)),
1210            (2, Some(3)),
1211            (-3, None),
1212            (100, Some(2)),
1213        ] {
1214            let expected = substring(&binary, start, length).unwrap();
1215            let expected = expected.as_binary::<i32>();
1216            let actual = substring(&view, start, length).unwrap();
1217            let actual = actual.as_binary_view();
1218            assert_eq!(
1219                expected.iter().collect::<Vec<_>>(),
1220                actual.iter().collect::<Vec<_>>(),
1221                "start={start} length={length:?}"
1222            );
1223        }
1224    }
1225
1226    #[test]
1227    fn string_view_slices_on_a_multi_byte_boundary() {
1228        // "é" and "ö" are two bytes, the CJK chars three, so 0/3/6 are the offsets that land
1229        // on a char boundary in both values.
1230        let values = vec![Some("héllo wörld"), Some("日本語"), None];
1231        let utf8 = StringArray::from(values.clone());
1232        let view = StringViewArray::from(values);
1233
1234        for (start, length) in [
1235            (0, Some(3)),
1236            (3, Some(3)),
1237            (0, Some(6)),
1238            (3, None),
1239            (-3, None),
1240            (-6, None),
1241        ] {
1242            let expected = substring(&utf8, start, length).unwrap();
1243            let expected = expected.as_string::<i32>();
1244            let actual = substring(&view, start, length).unwrap();
1245            let actual = actual.as_string_view();
1246            assert_eq!(
1247                expected.iter().collect::<Vec<_>>(),
1248                actual.iter().collect::<Vec<_>>(),
1249                "start={start} length={length:?}"
1250            );
1251        }
1252
1253        let actual = substring(&view, 0, Some(3)).unwrap();
1254        let actual = actual.as_string_view();
1255        assert_eq!(actual.value(0), "hé");
1256        assert_eq!(actual.value(1), "日");
1257    }
1258
1259    #[test]
1260    fn string_view_rejects_an_invalid_char_boundary() {
1261        let view = StringViewArray::from(vec![Some("E=mc²")]);
1262        let err = substring(&view, 0, Some(5)).unwrap_err().to_string();
1263        assert!(err.contains("invalid utf-8 boundary"), "{err}");
1264    }
1265
1266    #[test]
1267    fn dictionary_of_string_view() {
1268        let view = StringViewArray::from(vec![Some("hello world"), Some("bye")]);
1269        let dict = DictionaryArray::new(Int32Array::from(vec![0, 1, 0]), Arc::new(view));
1270
1271        let actual = substring(&dict, 0, Some(3)).unwrap();
1272        let actual = actual.as_any_dictionary();
1273
1274        let values = actual.values().as_string_view();
1275        assert_eq!(
1276            values.iter().collect::<Vec<_>>(),
1277            vec![Some("hel"), Some("bye")]
1278        );
1279    }
1280
1281    #[test]
1282    fn out_of_range_start_and_length_match_the_64_bit_arms() {
1283        // use 64 bit offset versions as expected behaviour for extreme start & length values
1284        // which should saturate and not wrap
1285        let values = vec![Some("hello"), Some("world"), None];
1286        let utf8 = StringArray::from(values.clone());
1287        let large = LargeStringArray::from(values.clone());
1288        let binary = BinaryArray::from_iter(values.iter().map(|v| v.map(|s| s.as_bytes())));
1289        let large_binary =
1290            LargeBinaryArray::from_iter(values.iter().map(|v| v.map(|s| s.as_bytes())));
1291
1292        // starts and lengths that do not survive a cast to i32
1293        let starts: [i64; 4] = [1 << 31, (1 << 31) + 5, 1 << 32, i64::MAX];
1294        let lengths: [Option<u64>; 3] = [None, Some(1 << 31), Some(u64::MAX)];
1295
1296        for start in starts {
1297            for length in lengths {
1298                let narrow = substring(&utf8, start, length).unwrap();
1299                let wide = substring(&large, start, length).unwrap();
1300                assert_eq!(
1301                    narrow.as_string::<i32>().iter().collect::<Vec<_>>(),
1302                    wide.as_string::<i64>().iter().collect::<Vec<_>>(),
1303                    "Utf8 and LargeUtf8 disagree at start {start}, length {length:?}"
1304                );
1305
1306                let narrow = substring(&binary, start, length).unwrap();
1307                let wide = substring(&large_binary, start, length).unwrap();
1308                assert_eq!(
1309                    narrow.as_binary::<i32>().iter().collect::<Vec<_>>(),
1310                    wide.as_binary::<i64>().iter().collect::<Vec<_>>(),
1311                    "Binary and LargeBinary disagree at start {start}, length {length:?}"
1312                );
1313            }
1314        }
1315
1316        // and the answer itself is the sensible one: skipping more characters
1317        // than the value holds leaves nothing behind.
1318        let out = substring(&utf8, 1 << 31, None).unwrap();
1319        assert_eq!(
1320            out.as_string::<i32>().iter().collect::<Vec<_>>(),
1321            vec![Some(""), Some(""), None]
1322        );
1323
1324        // starts that already fit are untouched
1325        let out = substring(&utf8, 3, None).unwrap();
1326        assert_eq!(
1327            out.as_string::<i32>().iter().collect::<Vec<_>>(),
1328            vec![Some("lo"), Some("ld"), None]
1329        );
1330    }
1331}