1use std::sync::Arc;
21
22use crate::strings::{
23 GenericStringArrayBuilder, STRING_VIEW_INIT_BLOCK_SIZE, STRING_VIEW_MAX_BLOCK_SIZE,
24 StringViewArrayBuilder, StringWriter, append_view,
25};
26use arrow::array::{
27 Array, ArrayRef, AsArray, GenericStringArray, NullBufferBuilder, OffsetSizeTrait,
28 StringViewArray, new_null_array,
29};
30use arrow::buffer::{Buffer, NullBuffer, OffsetBuffer, ScalarBuffer};
31use arrow::datatypes::DataType;
32use datafusion_common::Result;
33use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
34use datafusion_common::{ScalarValue, exec_err};
35use datafusion_expr::ColumnarValue;
36
37pub(crate) trait Trimmer {
43 fn trim<'a>(input: &'a str, pattern: &[char]) -> (&'a str, u32);
44
45 fn trim_ascii_char(input: &str, byte: u8) -> (&str, u32);
48}
49
50#[inline]
52fn leading_bytes(bytes: &[u8], byte: u8) -> usize {
53 bytes.iter().take_while(|&&b| b == byte).count()
54}
55
56#[inline]
58fn trailing_bytes(bytes: &[u8], byte: u8) -> usize {
59 bytes.iter().rev().take_while(|&&b| b == byte).count()
60}
61
62pub(crate) struct TrimLeft;
64
65impl Trimmer for TrimLeft {
66 #[inline]
67 fn trim<'a>(input: &'a str, pattern: &[char]) -> (&'a str, u32) {
68 if pattern.len() == 1 && pattern[0].is_ascii() {
69 return Self::trim_ascii_char(input, pattern[0] as u8);
70 }
71 let trimmed = input.trim_start_matches(pattern);
72 let offset = (input.len() - trimmed.len()) as u32;
73 (trimmed, offset)
74 }
75
76 #[inline]
77 fn trim_ascii_char(input: &str, byte: u8) -> (&str, u32) {
78 let start = leading_bytes(input.as_bytes(), byte);
79 (&input[start..], start as u32)
80 }
81}
82
83pub(crate) struct TrimRight;
85
86impl Trimmer for TrimRight {
87 #[inline]
88 fn trim<'a>(input: &'a str, pattern: &[char]) -> (&'a str, u32) {
89 if pattern.len() == 1 && pattern[0].is_ascii() {
90 return Self::trim_ascii_char(input, pattern[0] as u8);
91 }
92 let trimmed = input.trim_end_matches(pattern);
93 (trimmed, 0)
94 }
95
96 #[inline]
97 fn trim_ascii_char(input: &str, byte: u8) -> (&str, u32) {
98 let bytes = input.as_bytes();
99 let end = bytes.len() - trailing_bytes(bytes, byte);
100 (&input[..end], 0)
101 }
102}
103
104pub(crate) struct TrimBoth;
106
107impl Trimmer for TrimBoth {
108 #[inline]
109 fn trim<'a>(input: &'a str, pattern: &[char]) -> (&'a str, u32) {
110 if pattern.len() == 1 && pattern[0].is_ascii() {
111 return Self::trim_ascii_char(input, pattern[0] as u8);
112 }
113 let left_trimmed = input.trim_start_matches(pattern);
114 let offset = (input.len() - left_trimmed.len()) as u32;
115 let trimmed = left_trimmed.trim_end_matches(pattern);
116 (trimmed, offset)
117 }
118
119 #[inline]
120 fn trim_ascii_char(input: &str, byte: u8) -> (&str, u32) {
121 let bytes = input.as_bytes();
122 let start = leading_bytes(bytes, byte);
123 let end = bytes.len() - trailing_bytes(&bytes[start..], byte);
124 (&input[start..end], start as u32)
125 }
126}
127
128pub(crate) fn general_trim<T: OffsetSizeTrait, Tr: Trimmer>(
129 args: &[ArrayRef],
130 use_string_view: bool,
131) -> Result<ArrayRef> {
132 if use_string_view {
133 string_view_trim::<Tr>(args)
134 } else {
135 string_trim::<T, Tr>(args)
136 }
137}
138
139fn string_view_trim<Tr: Trimmer>(args: &[ArrayRef]) -> Result<ArrayRef> {
145 let string_view_array = as_string_view_array(&args[0])?;
146 let mut views_buf = Vec::with_capacity(string_view_array.len());
147 let mut null_builder = NullBufferBuilder::new(string_view_array.len());
148
149 match args.len() {
150 1 => {
151 for (src_str_opt, raw_view) in string_view_array
153 .iter()
154 .zip(string_view_array.views().iter())
155 {
156 if let Some(src_str) = src_str_opt {
157 let (trimmed, offset) = Tr::trim_ascii_char(src_str, b' ');
158 append_view(&mut views_buf, raw_view, trimmed, offset);
159 null_builder.append_non_null();
160 } else {
161 null_builder.append_null();
162 views_buf.push(0);
163 }
164 }
165 }
166 2 => {
167 let characters_array = as_string_view_array(&args[1])?;
168
169 if characters_array.len() == 1 {
170 if characters_array.is_null(0) {
172 return Ok(new_null_array(
173 &DataType::Utf8View,
174 string_view_array.len(),
175 ));
176 }
177
178 let pattern: Vec<char> = characters_array.value(0).chars().collect();
179 for (src_str_opt, raw_view) in string_view_array
180 .iter()
181 .zip(string_view_array.views().iter())
182 {
183 trim_and_append_view::<Tr>(
184 src_str_opt,
185 &pattern,
186 &mut views_buf,
187 &mut null_builder,
188 raw_view,
189 );
190 }
191 } else {
192 let mut pattern: Vec<char> = Vec::new();
194 for ((src_str_opt, raw_view), characters_opt) in string_view_array
195 .iter()
196 .zip(string_view_array.views().iter())
197 .zip(characters_array.iter())
198 {
199 if let (Some(src_str), Some(characters)) =
200 (src_str_opt, characters_opt)
201 {
202 pattern.clear();
203 pattern.extend(characters.chars());
204 let (trimmed, offset) = Tr::trim(src_str, &pattern);
205 append_view(&mut views_buf, raw_view, trimmed, offset);
206 null_builder.append_non_null();
207 } else {
208 null_builder.append_null();
209 views_buf.push(0);
210 }
211 }
212 }
213 }
214 other => {
215 return exec_err!(
216 "Function TRIM was called with {other} arguments. It requires at least 1 and at most 2."
217 );
218 }
219 }
220
221 let views_buf = ScalarBuffer::from(views_buf);
222 let nulls_buf = null_builder.finish();
223
224 unsafe {
229 let array = StringViewArray::new_unchecked(
230 views_buf,
231 string_view_array.data_buffers().to_vec(),
232 nulls_buf,
233 );
234 Ok(Arc::new(array) as ArrayRef)
235 }
236}
237
238#[inline]
248fn trim_and_append_view<Tr: Trimmer>(
249 src_str_opt: Option<&str>,
250 pattern: &[char],
251 views_buf: &mut Vec<u128>,
252 null_builder: &mut NullBufferBuilder,
253 original_view: &u128,
254) {
255 if let Some(src_str) = src_str_opt {
256 let (trimmed, offset) = Tr::trim(src_str, pattern);
257 append_view(views_buf, original_view, trimmed, offset);
258 null_builder.append_non_null();
259 } else {
260 null_builder.append_null();
261 views_buf.push(0);
262 }
263}
264
265fn build_trimmed<T: OffsetSizeTrait, F>(
277 string_array: &GenericStringArray<T>,
278 nulls: Option<NullBuffer>,
279 mut trim_row: F,
280) -> ArrayRef
281where
282 F: for<'a> FnMut(usize, &'a str) -> &'a str,
283{
284 let len = string_array.len();
285 let input_offsets = string_array.value_offsets();
286 let start = input_offsets.first().unwrap().as_usize();
287 let end = input_offsets.last().unwrap().as_usize();
288
289 let mut values: Vec<u8> = Vec::with_capacity(end - start);
290 let mut offsets: Vec<T> = Vec::with_capacity(len + 1);
291 offsets.push(T::usize_as(0));
292
293 match &nulls {
294 None => {
296 for i in 0..len {
297 let s = unsafe { string_array.value_unchecked(i) };
299 values.extend_from_slice(trim_row(i, s).as_bytes());
300 offsets.push(T::usize_as(values.len()));
301 }
302 }
303 Some(validity) => {
304 for i in 0..len {
305 if validity.is_valid(i) {
306 let s = unsafe { string_array.value_unchecked(i) };
308 values.extend_from_slice(trim_row(i, s).as_bytes());
309 }
310 offsets.push(T::usize_as(values.len()));
311 }
312 }
313 }
314
315 let offsets = OffsetBuffer::new(ScalarBuffer::from(offsets));
316 let array = unsafe {
319 GenericStringArray::<T>::new_unchecked(offsets, Buffer::from_vec(values), nulls)
320 };
321 Arc::new(array)
322}
323
324fn string_trim<T: OffsetSizeTrait, Tr: Trimmer>(args: &[ArrayRef]) -> Result<ArrayRef> {
330 let string_array = as_generic_string_array::<T>(&args[0])?;
331
332 match args.len() {
333 1 => {
334 Ok(build_trimmed(
336 string_array,
337 string_array.nulls().cloned(),
338 |_, s| Tr::trim_ascii_char(s, b' ').0,
339 ))
340 }
341 2 => {
342 let characters_array = as_generic_string_array::<T>(&args[1])?;
343
344 if characters_array.len() == 1 {
345 if characters_array.is_null(0) {
347 return Ok(new_null_array(
348 string_array.data_type(),
349 string_array.len(),
350 ));
351 }
352
353 let pattern: Vec<char> = characters_array.value(0).chars().collect();
354 return Ok(build_trimmed(
355 string_array,
356 string_array.nulls().cloned(),
357 |_, s| Tr::trim(s, &pattern).0,
358 ));
359 }
360
361 if characters_array.len() != string_array.len() {
364 return exec_err!(
365 "Function TRIM was called with mismatched argument lengths"
366 );
367 }
368
369 let nulls = NullBuffer::union(string_array.nulls(), characters_array.nulls());
371
372 let mut pattern: Vec<char> = Vec::new();
374 Ok(build_trimmed(string_array, nulls, |i, s| {
375 pattern.clear();
376 pattern.extend(characters_array.value(i).chars());
377 Tr::trim(s, &pattern).0
378 }))
379 }
380 other => {
381 exec_err!(
382 "Function TRIM was called with {other} arguments. It requires at least 1 and at most 2."
383 )
384 }
385 }
386}
387
388pub(crate) fn to_lower(args: &[ColumnarValue], name: &str) -> Result<ColumnarValue> {
389 case_conversion(args, true, name)
390}
391
392pub(crate) fn to_upper(args: &[ColumnarValue], name: &str) -> Result<ColumnarValue> {
393 case_conversion(args, false, name)
394}
395
396#[inline]
397fn unicode_case(s: &str, lower: bool) -> String {
398 if lower {
399 s.to_lowercase()
400 } else {
401 s.to_uppercase()
402 }
403}
404
405#[inline]
416fn write_unicode_case(w: &mut impl StringWriter, s: &str, lower: bool) {
417 if lower {
418 w.write_str(&s.to_lowercase());
419 } else {
420 for c in s.chars() {
421 for upper in c.to_uppercase() {
422 w.write_char(upper);
423 }
424 }
425 }
426}
427
428fn case_conversion(
429 args: &[ColumnarValue],
430 lower: bool,
431 name: &str,
432) -> Result<ColumnarValue> {
433 match &args[0] {
434 ColumnarValue::Array(array) => Ok(ColumnarValue::Array(
435 case_conversion_columnar_array(array, lower, name)?,
436 )),
437 ColumnarValue::Scalar(scalar) => Ok(ColumnarValue::Scalar(
438 case_conversion_scalar(scalar, lower, name)?,
439 )),
440 }
441}
442
443fn case_conversion_scalar(
444 scalar: &ScalarValue,
445 lower: bool,
446 name: &str,
447) -> Result<ScalarValue> {
448 match scalar {
449 ScalarValue::Utf8(a) => {
450 let result = a.as_ref().map(|x| unicode_case(x, lower));
451 Ok(ScalarValue::Utf8(result))
452 }
453 ScalarValue::LargeUtf8(a) => {
454 let result = a.as_ref().map(|x| unicode_case(x, lower));
455 Ok(ScalarValue::LargeUtf8(result))
456 }
457 ScalarValue::Utf8View(a) => {
458 let result = a.as_ref().map(|x| unicode_case(x, lower));
459 Ok(ScalarValue::Utf8View(result))
460 }
461 ScalarValue::Dictionary(key_type, value) => {
462 let converted = case_conversion_scalar(value.as_ref(), lower, name)?;
463 Ok(ScalarValue::Dictionary(
464 key_type.clone(),
465 Box::new(converted),
466 ))
467 }
468 other => exec_err!("Unsupported data type {other:?} for function {name}"),
469 }
470}
471
472fn case_conversion_columnar_array(
473 array: &ArrayRef,
474 lower: bool,
475 name: &str,
476) -> Result<ArrayRef> {
477 match array.data_type() {
478 DataType::Utf8 => case_conversion_array::<i32>(array, lower),
479 DataType::LargeUtf8 => case_conversion_array::<i64>(array, lower),
480 DataType::Utf8View => case_conversion_utf8view(array, lower),
481 DataType::Dictionary(_, _) => case_conversion_dictionary(array, lower, name),
482 other => exec_err!("Unsupported data type {other:?} for function {name}"),
483 }
484}
485
486fn case_conversion_utf8view(array: &ArrayRef, lower: bool) -> Result<ArrayRef> {
487 let string_array = as_string_view_array(array)?;
488 if string_array.is_ascii() {
489 return Ok(Arc::new(case_conversion_utf8view_ascii(
490 string_array,
491 lower,
492 )));
493 }
494 let item_len = string_array.len();
495 let nulls = string_array.nulls().cloned();
497 let mut builder = StringViewArrayBuilder::with_capacity(item_len);
498
499 if let Some(ref n) = nulls {
500 for i in 0..item_len {
501 if n.is_null(i) {
502 builder.try_append_placeholder()?;
503 } else {
504 let s = unsafe { string_array.value_unchecked(i) };
506 builder.try_append_value(&unicode_case(s, lower))?;
507 }
508 }
509 } else {
510 for i in 0..item_len {
511 let s = unsafe { string_array.value_unchecked(i) };
513 builder.try_append_value(&unicode_case(s, lower))?;
514 }
515 }
516
517 Ok(Arc::new(builder.finish(nulls)?))
518}
519
520fn case_conversion_dictionary(
521 array: &ArrayRef,
522 lower: bool,
523 name: &str,
524) -> Result<ArrayRef> {
525 let dictionary = array.as_any_dictionary();
526 let converted = case_conversion_columnar_array(dictionary.values(), lower, name)?;
527 Ok(dictionary.with_values(converted))
528}
529
530fn case_conversion_array<O: OffsetSizeTrait>(
531 array: &ArrayRef,
532 lower: bool,
533) -> Result<ArrayRef> {
534 const PRE_ALLOC_BYTES: usize = 8;
535
536 let string_array = as_generic_string_array::<O>(array)?;
537 if string_array.is_ascii() {
538 return case_conversion_ascii_array::<O>(string_array, lower);
539 }
540
541 let item_len = string_array.len();
543 let offsets = string_array.value_offsets();
544 let start = offsets.first().unwrap().as_usize();
545 let end = offsets.last().unwrap().as_usize();
546 let capacity = (end - start) + PRE_ALLOC_BYTES;
547 let nulls = string_array.nulls().cloned();
549 let mut builder = GenericStringArrayBuilder::<O>::with_capacity(item_len, capacity);
550
551 if let Some(ref n) = nulls {
552 for i in 0..item_len {
553 if n.is_null(i) {
554 builder.try_append_placeholder()?;
555 } else {
556 let s = unsafe { string_array.value_unchecked(i) };
558 builder.try_append_with(|w| write_unicode_case(w, s, lower))?;
559 }
560 }
561 } else {
562 for i in 0..item_len {
563 let s = unsafe { string_array.value_unchecked(i) };
565 builder.try_append_with(|w| write_unicode_case(w, s, lower))?;
566 }
567 }
568 Ok(Arc::new(builder.finish(nulls)?))
569}
570
571fn case_conversion_utf8view_ascii(
573 array: &StringViewArray,
574 lower: bool,
575) -> StringViewArray {
576 if lower {
578 case_conversion_utf8view_ascii_inner(array, u8::to_ascii_lowercase)
579 } else {
580 case_conversion_utf8view_ascii_inner(array, u8::to_ascii_uppercase)
581 }
582}
583
584fn case_conversion_utf8view_ascii_inner<F: Fn(&u8) -> u8>(
590 array: &StringViewArray,
591 convert: F,
592) -> StringViewArray {
593 let item_len = array.len();
594 let views = array.views();
595 let data_buffers = array.data_buffers();
596 let nulls = array.nulls();
597
598 let mut new_views: Vec<u128> = Vec::with_capacity(item_len);
599 let mut in_progress: Vec<u8> = Vec::new();
603 let mut completed: Vec<Buffer> = Vec::new();
604 let mut block_size: u32 = STRING_VIEW_INIT_BLOCK_SIZE;
605
606 for i in 0..item_len {
607 if nulls.is_some_and(|n| n.is_null(i)) {
608 new_views.push(0);
611 continue;
612 }
613 let view = views[i];
614 let len = view as u32 as usize;
616 if len == 0 {
617 new_views.push(0);
618 continue;
619 }
620 let mut bytes = view.to_le_bytes();
621 if len <= 12 {
622 for b in &mut bytes[4..4 + len] {
625 *b = convert(b);
626 }
627 new_views.push(u128::from_le_bytes(bytes));
628 } else {
629 let required_cap = in_progress.len() + len;
635 if in_progress.capacity() < required_cap {
636 if !in_progress.is_empty() {
637 completed.push(Buffer::from_vec(std::mem::take(&mut in_progress)));
638 }
639 if block_size < STRING_VIEW_MAX_BLOCK_SIZE {
640 block_size = block_size.saturating_mul(2);
641 }
642 let to_reserve = len.max(block_size as usize);
643 #[expect(
644 clippy::disallowed_methods,
645 reason = "StringView's block size bounds growth, so reserve cannot overflow capacity arithmetically. This hot loop intentionally avoids the extra `try_reserve` checks. It remains subject to allocator failure/OOM, which must be managed externally."
646 )]
647 in_progress.reserve(to_reserve);
648 }
649
650 let buffer_index: u32 = i32::try_from(completed.len())
653 .expect("buffer count exceeds i32::MAX")
654 as u32;
655 let new_offset: u32 =
656 i32::try_from(in_progress.len()).expect("offset exceeds i32::MAX") as u32;
657
658 let src_buffer_index =
661 u32::from_le_bytes(bytes[8..12].try_into().unwrap()) as usize;
662 let src_offset =
663 u32::from_le_bytes(bytes[12..16].try_into().unwrap()) as usize;
664 let src =
665 &data_buffers[src_buffer_index].as_slice()[src_offset..src_offset + len];
666
667 let prefix_start = in_progress.len();
668 in_progress.extend(src.iter().map(&convert));
669
670 let prefix: [u8; 4] = in_progress[prefix_start..prefix_start + 4]
674 .try_into()
675 .unwrap();
676 bytes[4..8].copy_from_slice(&prefix);
677 bytes[8..12].copy_from_slice(&buffer_index.to_le_bytes());
678 bytes[12..16].copy_from_slice(&new_offset.to_le_bytes());
679 new_views.push(u128::from_le_bytes(bytes));
680 }
681 }
682
683 if !in_progress.is_empty() {
684 completed.push(Buffer::from_vec(in_progress));
685 }
686
687 unsafe {
693 StringViewArray::new_unchecked(
694 ScalarBuffer::from(new_views),
695 completed,
696 array.nulls().cloned(),
697 )
698 }
699}
700
701fn case_conversion_ascii_array<O: OffsetSizeTrait>(
706 string_array: &GenericStringArray<O>,
707 lower: bool,
708) -> Result<ArrayRef> {
709 let value_offsets = string_array.value_offsets();
710 let start = value_offsets.first().unwrap().as_usize();
711 let end = value_offsets.last().unwrap().as_usize();
712 let relevant = &string_array.value_data()[start..end];
713
714 let converted: Vec<u8> = if lower {
715 relevant.iter().map(u8::to_ascii_lowercase).collect()
716 } else {
717 relevant.iter().map(u8::to_ascii_uppercase).collect()
718 };
719 let values = Buffer::from_vec(converted);
720
721 let offsets = string_array
723 .offsets()
724 .clone()
725 .subtract(string_array.offsets()[0]);
726
727 let nulls = string_array.nulls().cloned();
728 Ok(Arc::new(unsafe {
731 GenericStringArray::<O>::new_unchecked(offsets, values, nulls)
732 }))
733}