1use 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
33pub 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 length.map(u64_to_i64_saturating),
89 ),
90 DataType::Binary => byte_substring(
91 array.as_binary::<i32>(),
92 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 length.map(u64_to_i64_saturating),
107 ),
108 DataType::Utf8 => byte_substring(
109 array.as_string::<i32>(),
110 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
123pub 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 Ok(substring_by_char_impl(array, length, |val| {
162 ascii_bounds(val, start, length)
163 }))
164 } else {
165 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
173fn 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#[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#[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 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 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
261fn 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 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 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 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 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 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 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 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 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 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 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 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 (0, None, input.clone()),
569 (0, Some(0), vec![Some(&[]), None, Some(&[])]),
571 (1000, Some(0), vec![Some(&[]), None, Some(&[])]),
573 (-1000, None, input.clone()),
575 (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 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 (0, None, input.clone()),
607 (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 (-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 (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 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 let array = GenericBinaryArray::<O>::new(offsets, values, nulls).slice(1, 2);
665 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 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 (0, None, input.clone()),
696 (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 (-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 (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 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 (0, None, input.clone()),
735 (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 (-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 (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 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 let array = FixedSizeBinaryArray::new(5, Buffer::from(values), nulls).slice(1, 2);
775 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 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 (0, None, input.clone()),
797 (0, Some(0), vec![Some(""), None, Some("")]),
799 (1000, Some(0), vec![Some(""), None, Some("")]),
801 (-1000, None, input.clone()),
803 (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 let base_case = gen_test_cases!(vec!["", "", ""], (0, None, vec!["", "", ""]));
828 let cases = gen_test_cases!(
829 input,
830 (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 (-1, None, vec!["o", "", "d"]),
838 (-2, None, vec!["lo", "", "rd"]),
839 (-3, None, vec!["llo", "", "ord"]),
840 (-10, None, input.clone()),
841 (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 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 let array = GenericStringArray::<O>::new(offsets, values, nulls).slice(1, 2);
889 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 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 (0, None, input.clone()),
917 (0, Some(0), vec![Some(""), None, Some("")]),
919 (1000, Some(0), vec![Some(""), None, Some("")]),
921 (-1000, None, input.clone()),
923 (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 let base_case = gen_test_cases!(vec!["", "", ""], (0, None, vec!["", "", ""]));
948 let cases = gen_test_cases!(
949 input,
950 (0, None, input.clone()),
952 (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 (-1, None, vec!["o", "", "T"]),
959 (-2, None, vec!["lo", "", ":T"]),
960 (-4, None, vec!["ello", "", "⊢x:T"]),
961 (-10, None, input.clone()),
962 (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 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 (0, None, input.clone()),
997 (1, None, vec![Some("ello"), None, Some(""), Some("ust")]),
999 (4, None, vec![Some("o"), None, Some(""), Some("")]),
1000 (1000, None, vec![Some(""), None, Some(""), Some("")]),
1002 (-1, None, vec![Some("o"), None, Some(""), Some("t")]),
1004 (-4, None, vec![Some("ello"), None, Some(""), Some("rust")]),
1005 (-1000, None, input.clone()),
1007 (0, Some(0), vec![Some(""), None, Some(""), Some("")]),
1009 (1, Some(2), vec![Some("el"), None, Some(""), Some("us")]),
1011 (-4, Some(2), vec![Some("el"), None, Some(""), Some("ru")]),
1012 (0, Some(1000), input.clone()),
1014 (
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 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 let array = GenericStringArray::<O>::new(offsets, values, nulls).slice(1, 2);
1055 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 #[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 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 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 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 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 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 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 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}