Skip to main content

datafusion_functions/string/
split_part.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
18use crate::strings::{
19    BulkNullStringArrayBuilder, GenericStringArrayBuilder, StringViewArrayBuilder,
20};
21use crate::utils::utf8_to_str_type;
22use arrow::array::{
23    Array, ArrayRef, AsArray, ByteView, Int64Array, StringArrayType, StringViewArray,
24    make_view, new_null_array,
25};
26use arrow::buffer::{NullBuffer, ScalarBuffer};
27use arrow::datatypes::DataType;
28use datafusion_common::ScalarValue;
29use datafusion_common::cast::as_int64_array;
30use datafusion_common::types::{NativeType, logical_int64, logical_string};
31use datafusion_common::{Result, exec_datafusion_err, exec_err};
32use datafusion_expr::{
33    Coercion, ColumnarValue, Documentation, TypeSignatureClass, Volatility,
34};
35use datafusion_expr::{ScalarFunctionArgs, ScalarUDFImpl, Signature};
36use datafusion_macros::user_doc;
37use memchr::memmem;
38use std::sync::Arc;
39
40#[user_doc(
41    doc_section(label = "String Functions"),
42    description = "Splits a string based on a specified delimiter and returns the substring in the specified position.",
43    syntax_example = "split_part(str, delimiter, pos)",
44    sql_example = r#"```sql
45> select split_part('1.2.3.4.5', '.', 3);
46+--------------------------------------------------+
47| split_part(Utf8("1.2.3.4.5"),Utf8("."),Int64(3)) |
48+--------------------------------------------------+
49| 3                                                |
50+--------------------------------------------------+
51```"#,
52    standard_argument(name = "str", prefix = "String"),
53    argument(name = "delimiter", description = "String or character to split on."),
54    argument(
55        name = "pos",
56        description = "Position of the part to return (counting from 1). Negative values count backward from the end of the string."
57    )
58)]
59#[derive(Debug, PartialEq, Eq, Hash)]
60pub struct SplitPartFunc {
61    signature: Signature,
62}
63
64impl Default for SplitPartFunc {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl SplitPartFunc {
71    pub fn new() -> Self {
72        Self {
73            signature: Signature::coercible(
74                vec![
75                    Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
76                    Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
77                    Coercion::new_implicit(
78                        TypeSignatureClass::Native(logical_int64()),
79                        vec![TypeSignatureClass::Integer],
80                        NativeType::Int64,
81                    ),
82                ],
83                Volatility::Immutable,
84            ),
85        }
86    }
87}
88
89impl ScalarUDFImpl for SplitPartFunc {
90    fn name(&self) -> &str {
91        "split_part"
92    }
93
94    fn signature(&self) -> &Signature {
95        &self.signature
96    }
97
98    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
99        if arg_types[0] == DataType::Utf8View {
100            Ok(DataType::Utf8View)
101        } else {
102            utf8_to_str_type(&arg_types[0], "split_part")
103        }
104    }
105
106    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
107        let ScalarFunctionArgs { args, .. } = args;
108
109        // Fast path: array string, scalar delimiter and position.
110        if let (
111            ColumnarValue::Array(string_array),
112            ColumnarValue::Scalar(delim_scalar),
113            ColumnarValue::Scalar(pos_scalar),
114        ) = (&args[0], &args[1], &args[2])
115        {
116            return split_part_scalar(string_array, delim_scalar, pos_scalar);
117        }
118
119        // First, determine if any of the arguments is an Array
120        let len = args.iter().find_map(|arg| match arg {
121            ColumnarValue::Array(a) => Some(a.len()),
122            _ => None,
123        });
124
125        let inferred_length = len.unwrap_or(1);
126        let is_scalar = len.is_none();
127
128        // Convert all ColumnarValues to ArrayRefs
129        let args = args
130            .iter()
131            .map(|arg| match arg {
132                ColumnarValue::Scalar(scalar) => scalar.to_array_of_size(inferred_length),
133                ColumnarValue::Array(array) => Ok(Arc::clone(array)),
134            })
135            .collect::<Result<Vec<_>>>()?;
136
137        // Unpack the ArrayRefs from the arguments
138        let n_array = as_int64_array(&args[2])?;
139
140        // Dispatch on delimiter type for a given string array and builder.
141        macro_rules! split_part_for_delimiter_type {
142            ($str_arr:expr, $builder:expr) => {
143                match args[1].data_type() {
144                    DataType::Utf8View => split_part_impl(
145                        $str_arr,
146                        &args[1].as_string_view(),
147                        n_array,
148                        $builder,
149                    ),
150                    DataType::Utf8 => split_part_impl(
151                        $str_arr,
152                        &args[1].as_string::<i32>(),
153                        n_array,
154                        $builder,
155                    ),
156                    DataType::LargeUtf8 => split_part_impl(
157                        $str_arr,
158                        &args[1].as_string::<i64>(),
159                        n_array,
160                        $builder,
161                    ),
162                    other => {
163                        exec_err!("Unsupported delimiter type {other:?} for split_part")
164                    }
165                }
166            };
167        }
168
169        let result = match args[0].data_type() {
170            DataType::Utf8View => split_part_for_delimiter_type!(
171                &args[0].as_string_view(),
172                StringViewArrayBuilder::with_capacity(inferred_length)
173            ),
174            DataType::Utf8 => {
175                let str_arr = &args[0].as_string::<i32>();
176                // Conservative under-estimate for data capacity: split_part
177                // output is typically much smaller than the input, so avoid
178                // pre-allocating the full input data size.
179                split_part_for_delimiter_type!(
180                    str_arr,
181                    GenericStringArrayBuilder::<i32>::with_capacity(
182                        inferred_length,
183                        inferred_length,
184                    )
185                )
186            }
187            DataType::LargeUtf8 => {
188                let str_arr = &args[0].as_string::<i64>();
189                // Conservative under-estimate; see Utf8 comment above.
190                split_part_for_delimiter_type!(
191                    str_arr,
192                    GenericStringArrayBuilder::<i64>::with_capacity(
193                        inferred_length,
194                        inferred_length,
195                    )
196                )
197            }
198            other => exec_err!("Unsupported string type {other:?} for split_part"),
199        };
200        if is_scalar {
201            // If all inputs are scalar, keep the output as scalar
202            let result = result.and_then(|arr| ScalarValue::try_from_array(&arr, 0));
203            result.map(ColumnarValue::Scalar)
204        } else {
205            result.map(ColumnarValue::Array)
206        }
207    }
208
209    fn documentation(&self) -> Option<&Documentation> {
210        self.doc()
211    }
212}
213
214/// Finds the `n`th (0-based) split part of `string` by `delimiter`.
215#[inline]
216fn split_nth<'a>(string: &'a str, delimiter: &str, n: usize) -> Option<&'a str> {
217    if delimiter.len() == 1 {
218        // A single-byte UTF-8 string is always ASCII, so we can safely cast
219        // just the first byte to a character. `str::split(char)` internally
220        // uses memchr::memchr and is notably faster than `str::split(&str)`,
221        // even for a single character string.
222        string.split(delimiter.as_bytes()[0] as char).nth(n)
223    } else {
224        string.split(delimiter).nth(n)
225    }
226}
227
228/// Like `split_nth` but splits from the right (`n` is 0-based from the end).
229#[inline]
230fn rsplit_nth<'a>(string: &'a str, delimiter: &str, n: usize) -> Option<&'a str> {
231    if delimiter.len() == 1 {
232        // A single-byte UTF-8 string is always ASCII, so we can safely cast
233        // just the first byte to a character. `str::rsplit(char)` internally
234        // uses memchr::memrchr and is notably faster than `str::rsplit(&str)`,
235        // even for a single character string.
236        string.rsplit(delimiter.as_bytes()[0] as char).nth(n)
237    } else {
238        string.rsplit(delimiter).nth(n)
239    }
240}
241
242/// Fast path for `split_part(array, scalar_delimiter, scalar_position)`.
243fn split_part_scalar(
244    string_array: &ArrayRef,
245    delim_scalar: &ScalarValue,
246    pos_scalar: &ScalarValue,
247) -> Result<ColumnarValue> {
248    // Empty input array → empty result.
249    if string_array.is_empty() {
250        return Ok(ColumnarValue::Array(new_null_array(
251            string_array.data_type(),
252            0,
253        )));
254    }
255
256    let delimiter = delim_scalar.try_as_str().ok_or_else(|| {
257        exec_datafusion_err!(
258            "Unsupported delimiter type {:?} for split_part",
259            delim_scalar.data_type()
260        )
261    })?;
262
263    let position = match pos_scalar {
264        ScalarValue::Int64(v) => *v,
265        other => {
266            return exec_err!(
267                "Unsupported position type {:?} for split_part",
268                other.data_type()
269            );
270        }
271    };
272
273    // Null delimiter or position → every row is null.
274    let (Some(delimiter), Some(position)) = (delimiter, position) else {
275        return Ok(ColumnarValue::Array(new_null_array(
276            string_array.data_type(),
277            string_array.len(),
278        )));
279    };
280
281    if position == 0 {
282        return exec_err!("field position must not be zero");
283    }
284
285    let result = match string_array.data_type() {
286        DataType::Utf8View => {
287            split_part_scalar_view(string_array.as_string_view(), delimiter, position)
288        }
289        DataType::Utf8 => {
290            let arr = string_array.as_string::<i32>();
291            // Conservative under-estimate for data capacity: split_part output
292            // is typically much smaller than the input, so avoid pre-allocating
293            // the full input data size.
294            split_part_scalar_impl(
295                arr,
296                delimiter,
297                position,
298                GenericStringArrayBuilder::<i32>::with_capacity(arr.len(), arr.len()),
299            )
300        }
301        DataType::LargeUtf8 => {
302            let arr = string_array.as_string::<i64>();
303            // Conservative under-estimate; see Utf8 comment above.
304            split_part_scalar_impl(
305                arr,
306                delimiter,
307                position,
308                GenericStringArrayBuilder::<i64>::with_capacity(arr.len(), arr.len()),
309            )
310        }
311        other => exec_err!("Unsupported string type {other:?} for split_part"),
312    }?;
313
314    Ok(ColumnarValue::Array(result))
315}
316
317/// Inner implementation for the scalar-delimiter, scalar-position fast path.
318/// Constructing a `memmem::Finder` is somewhat expensive but it's a win when
319/// done once and amortized over the entire batch.
320fn split_part_scalar_impl<'a, S, B>(
321    string_array: S,
322    delimiter: &str,
323    position: i64,
324    builder: B,
325) -> Result<ArrayRef>
326where
327    S: StringArrayType<'a> + Copy,
328    B: BulkNullStringArrayBuilder,
329{
330    if delimiter.is_empty() {
331        // PostgreSQL: empty delimiter treats input as a single field,
332        // so only position 1 or -1 returns the input string.
333        return if position == 1 || position == -1 {
334            map_strings(string_array, builder, Some)
335        } else {
336            map_strings(string_array, builder, |_| None)
337        };
338    }
339
340    let delim_bytes = delimiter.as_bytes();
341    let delim_len = delimiter.len();
342
343    if position > 0 {
344        let idx: usize = (position - 1).try_into().map_err(|_| {
345            exec_datafusion_err!(
346                "split_part index {position} exceeds maximum supported value"
347            )
348        })?;
349        let finder = memmem::Finder::new(delim_bytes);
350        map_strings(string_array, builder, |s| {
351            split_nth_finder(s, &finder, delim_len, idx)
352        })
353    } else {
354        let idx: usize = (position.unsigned_abs() - 1).try_into().map_err(|_| {
355            exec_datafusion_err!(
356                "split_part index {position} exceeds minimum supported value"
357            )
358        })?;
359        let finder_rev = memmem::FinderRev::new(delim_bytes);
360        map_strings(string_array, builder, |s| {
361            rsplit_nth_finder(s, &finder_rev, delim_len, idx)
362        })
363    }
364}
365
366/// Applies `f` to each non-null string in `string_array`, appending the
367/// result (or `""` when `f` returns `None`) to `builder`.
368#[inline]
369fn map_strings<'a, S, B, F>(string_array: S, mut builder: B, f: F) -> Result<ArrayRef>
370where
371    S: StringArrayType<'a> + Copy,
372    B: BulkNullStringArrayBuilder,
373    F: Fn(&'a str) -> Option<&'a str>,
374{
375    let item_len = string_array.len();
376    let nulls = string_array.nulls().cloned();
377
378    if let Some(ref n) = nulls {
379        for i in 0..item_len {
380            if n.is_null(i) {
381                builder.append_placeholder();
382            } else {
383                // SAFETY: `n.is_null(i)` was false in the branch above.
384                let s = unsafe { string_array.value_unchecked(i) };
385                builder.append_value(f(s).unwrap_or(""));
386            }
387        }
388    } else {
389        for i in 0..item_len {
390            // SAFETY: no null buffer means every index is valid.
391            let s = unsafe { string_array.value_unchecked(i) };
392            builder.append_value(f(s).unwrap_or(""));
393        }
394    }
395
396    builder.finish(nulls)
397}
398
399/// Finds the `n`th (0-based) split part using a pre-built `memmem::Finder`.
400#[inline]
401fn split_nth_finder<'a>(
402    string: &'a str,
403    finder: &memmem::Finder,
404    delim_len: usize,
405    n: usize,
406) -> Option<&'a str> {
407    let bytes = string.as_bytes();
408    let mut start = 0;
409    for _ in 0..n {
410        let pos = finder.find(&bytes[start..])?;
411        start += pos + delim_len
412    }
413    match finder.find(&bytes[start..]) {
414        Some(pos) => Some(&string[start..start + pos]),
415        None => Some(&string[start..]),
416    }
417}
418
419/// Like `split_nth_finder` but splits from the right (`n` is 0-based from
420/// the end).
421#[inline]
422fn rsplit_nth_finder<'a>(
423    string: &'a str,
424    finder: &memmem::FinderRev,
425    delim_len: usize,
426    n: usize,
427) -> Option<&'a str> {
428    let bytes = string.as_bytes();
429    let mut end = bytes.len();
430    for _ in 0..n {
431        let pos = finder.rfind(&bytes[..end])?;
432        end = pos
433    }
434    match finder.rfind(&bytes[..end]) {
435        Some(pos) => Some(&string[pos + delim_len..end]),
436        None => Some(&string[..end]),
437    }
438}
439
440/// Zero-copy scalar fast path for `StringViewArray` inputs.
441///
442/// Instead of copying substring bytes into a new buffer, constructs
443/// `StringView` entries that point back into the original array's data
444/// buffers.
445fn split_part_scalar_view(
446    string_view_array: &StringViewArray,
447    delimiter: &str,
448    position: i64,
449) -> Result<ArrayRef> {
450    let len = string_view_array.len();
451    let mut views_buf = Vec::with_capacity(len);
452    let views = string_view_array.views();
453
454    if delimiter.is_empty() {
455        // PostgreSQL: empty delimiter treats input as a single field.
456        let empty_view = make_view(b"", 0, 0);
457        let return_input = position == 1 || position == -1;
458        for i in 0..len {
459            if string_view_array.is_null(i) {
460                views_buf.push(0);
461            } else if return_input {
462                views_buf.push(views[i]);
463            } else {
464                views_buf.push(empty_view);
465            }
466        }
467    } else if position > 0 {
468        let idx: usize = (position - 1).try_into().map_err(|_| {
469            exec_datafusion_err!(
470                "split_part index {position} exceeds maximum supported value"
471            )
472        })?;
473        let finder = memmem::Finder::new(delimiter.as_bytes());
474        split_view_loop(string_view_array, views, &mut views_buf, |s| {
475            split_nth_finder(s, &finder, delimiter.len(), idx)
476        });
477    } else {
478        let idx: usize = (position.unsigned_abs() - 1).try_into().map_err(|_| {
479            exec_datafusion_err!(
480                "split_part index {position} exceeds minimum supported value"
481            )
482        })?;
483        let finder_rev = memmem::FinderRev::new(delimiter.as_bytes());
484        split_view_loop(string_view_array, views, &mut views_buf, |s| {
485            rsplit_nth_finder(s, &finder_rev, delimiter.len(), idx)
486        });
487    }
488
489    let views_buf = ScalarBuffer::from(views_buf);
490
491    // Nulls pass through unchanged, so we can use the input's null array.
492    let nulls = string_view_array.nulls().cloned();
493
494    // Safety: each view is either copied unchanged from the input, or built
495    // by `substr_view` from a substring that is a contiguous sub-range of the
496    // original string value stored in the input's data buffers.
497    unsafe {
498        Ok(Arc::new(StringViewArray::new_unchecked(
499            views_buf,
500            string_view_array.data_buffers().to_vec(),
501            nulls,
502        )) as ArrayRef)
503    }
504}
505
506/// Creates a `StringView` referencing a substring of an existing view's buffer.
507/// For substrings ≤ 12 bytes, creates an inline view instead.
508#[inline]
509fn substr_view(original_view: &u128, substr: &str, start_offset: u32) -> u128 {
510    if substr.len() > 12 {
511        let view = ByteView::from(*original_view);
512        make_view(
513            substr.as_bytes(),
514            view.buffer_index,
515            view.offset + start_offset,
516        )
517    } else {
518        make_view(substr.as_bytes(), 0, 0)
519    }
520}
521
522/// Applies `split_fn` to each non-null string and appends the resulting view to
523/// `views_buf`.
524#[inline(always)]
525fn split_view_loop<F>(
526    string_view_array: &StringViewArray,
527    views: &[u128],
528    views_buf: &mut Vec<u128>,
529    split_fn: F,
530) where
531    F: Fn(&str) -> Option<&str>,
532{
533    let empty_view = make_view(b"", 0, 0);
534    for (i, raw_view) in views.iter().enumerate() {
535        if string_view_array.is_null(i) {
536            views_buf.push(0);
537            continue;
538        }
539        let string = string_view_array.value(i);
540        match split_fn(string) {
541            Some(substr) => {
542                let start_offset = substr.as_ptr() as usize - string.as_ptr() as usize;
543                views_buf.push(substr_view(raw_view, substr, start_offset as u32));
544            }
545            None => views_buf.push(empty_view),
546        }
547    }
548}
549
550fn split_part_impl<'a, StringArrType, DelimiterArrType, B>(
551    string_array: &StringArrType,
552    delimiter_array: &DelimiterArrType,
553    n_array: &Int64Array,
554    mut builder: B,
555) -> Result<ArrayRef>
556where
557    StringArrType: StringArrayType<'a>,
558    DelimiterArrType: StringArrayType<'a>,
559    B: BulkNullStringArrayBuilder,
560{
561    let nulls = NullBuffer::union_many([
562        string_array.nulls(),
563        delimiter_array.nulls(),
564        n_array.nulls(),
565    ]);
566
567    if let Some(ref n) = nulls {
568        for i in 0..string_array.len() {
569            if n.is_null(i) {
570                builder.append_placeholder();
571                continue;
572            }
573
574            // SAFETY: the union null buffer is valid at `i`, so each input is valid.
575            let string = unsafe { string_array.value_unchecked(i) };
576            let delimiter = unsafe { delimiter_array.value_unchecked(i) };
577            let position = unsafe { n_array.value_unchecked(i) };
578            append_split_part(string, delimiter, position, &mut builder)?;
579        }
580    } else {
581        for i in 0..string_array.len() {
582            // SAFETY: no input has a null buffer, so every index is valid.
583            let string = unsafe { string_array.value_unchecked(i) };
584            let delimiter = unsafe { delimiter_array.value_unchecked(i) };
585            let position = unsafe { n_array.value_unchecked(i) };
586            append_split_part(string, delimiter, position, &mut builder)?;
587        }
588    }
589
590    builder.finish(nulls)
591}
592
593#[inline]
594fn append_split_part<B: BulkNullStringArrayBuilder>(
595    string: &str,
596    delimiter: &str,
597    n: i64,
598    builder: &mut B,
599) -> Result<()> {
600    let result = match n.cmp(&0) {
601        std::cmp::Ordering::Greater => {
602            let idx: usize = (n - 1).try_into().map_err(|_| {
603                exec_datafusion_err!(
604                    "split_part index {n} exceeds maximum supported value"
605                )
606            })?;
607            if delimiter.is_empty() {
608                // Match PostgreSQL's behavior: empty delimiter treats input
609                // as a single field, so only position 1 returns data.
610                (n == 1).then_some(string)
611            } else {
612                split_nth(string, delimiter, idx)
613            }
614        }
615        std::cmp::Ordering::Less => {
616            let idx: usize = (n.unsigned_abs() - 1).try_into().map_err(|_| {
617                exec_datafusion_err!(
618                    "split_part index {n} exceeds minimum supported value"
619                )
620            })?;
621            if delimiter.is_empty() {
622                // Match PostgreSQL's behavior: empty delimiter treats input
623                // as a single field, so only position -1 returns data.
624                (n == -1).then_some(string)
625            } else {
626                rsplit_nth(string, delimiter, idx)
627            }
628        }
629        std::cmp::Ordering::Equal => {
630            return exec_err!("field position must not be zero");
631        }
632    };
633    builder.append_value(result.unwrap_or(""));
634    Ok(())
635}
636
637#[cfg(test)]
638mod tests {
639    use arrow::array::{Array, AsArray, StringArray, StringViewArray};
640    use arrow::datatypes::DataType::Utf8;
641
642    use datafusion_common::ScalarValue;
643    use datafusion_common::{Result, exec_err};
644    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
645
646    use crate::string::split_part::SplitPartFunc;
647    use crate::utils::test::test_function;
648
649    #[test]
650    fn test_functions() -> Result<()> {
651        test_function!(
652            SplitPartFunc::new(),
653            vec![
654                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(
655                    "abc~@~def~@~ghi"
656                )))),
657                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("~@~")))),
658                ColumnarValue::Scalar(ScalarValue::Int64(Some(2))),
659            ],
660            Ok(Some("def")),
661            &str,
662            Utf8,
663            StringArray
664        );
665        test_function!(
666            SplitPartFunc::new(),
667            vec![
668                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(
669                    "abc~@~def~@~ghi"
670                )))),
671                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("~@~")))),
672                ColumnarValue::Scalar(ScalarValue::Int64(Some(20))),
673            ],
674            Ok(Some("")),
675            &str,
676            Utf8,
677            StringArray
678        );
679        test_function!(
680            SplitPartFunc::new(),
681            vec![
682                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(
683                    "abc~@~def~@~ghi"
684                )))),
685                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("~@~")))),
686                ColumnarValue::Scalar(ScalarValue::Int64(Some(-1))),
687            ],
688            Ok(Some("ghi")),
689            &str,
690            Utf8,
691            StringArray
692        );
693        test_function!(
694            SplitPartFunc::new(),
695            vec![
696                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(
697                    "abc~@~def~@~ghi"
698                )))),
699                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("~@~")))),
700                ColumnarValue::Scalar(ScalarValue::Int64(Some(0))),
701            ],
702            exec_err!("field position must not be zero"),
703            &str,
704            Utf8,
705            StringArray
706        );
707        test_function!(
708            SplitPartFunc::new(),
709            vec![
710                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(
711                    "abc~@~def~@~ghi"
712                )))),
713                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("~@~")))),
714                ColumnarValue::Scalar(ScalarValue::Int64(Some(i64::MIN))),
715            ],
716            Ok(Some("")),
717            &str,
718            Utf8,
719            StringArray
720        );
721        // Edge cases with delimiters
722        test_function!(
723            SplitPartFunc::new(),
724            vec![
725                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
726                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(",")))),
727                ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
728            ],
729            Ok(Some("a")),
730            &str,
731            Utf8,
732            StringArray
733        );
734        test_function!(
735            SplitPartFunc::new(),
736            vec![
737                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
738                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(",")))),
739                ColumnarValue::Scalar(ScalarValue::Int64(Some(3))),
740            ],
741            Ok(Some("")),
742            &str,
743            Utf8,
744            StringArray
745        );
746        test_function!(
747            SplitPartFunc::new(),
748            vec![
749                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
750                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("")))),
751                ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
752            ],
753            Ok(Some("a,b")),
754            &str,
755            Utf8,
756            StringArray
757        );
758        test_function!(
759            SplitPartFunc::new(),
760            vec![
761                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
762                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("")))),
763                ColumnarValue::Scalar(ScalarValue::Int64(Some(2))),
764            ],
765            Ok(Some("")),
766            &str,
767            Utf8,
768            StringArray
769        );
770        test_function!(
771            SplitPartFunc::new(),
772            vec![
773                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
774                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(" ")))),
775                ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
776            ],
777            Ok(Some("a,b")),
778            &str,
779            Utf8,
780            StringArray
781        );
782        test_function!(
783            SplitPartFunc::new(),
784            vec![
785                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
786                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(" ")))),
787                ColumnarValue::Scalar(ScalarValue::Int64(Some(2))),
788            ],
789            Ok(Some("")),
790            &str,
791            Utf8,
792            StringArray
793        );
794
795        // Edge cases with delimiters with negative n
796        test_function!(
797            SplitPartFunc::new(),
798            vec![
799                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
800                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("")))),
801                ColumnarValue::Scalar(ScalarValue::Int64(Some(-1))),
802            ],
803            Ok(Some("a,b")),
804            &str,
805            Utf8,
806            StringArray
807        );
808        test_function!(
809            SplitPartFunc::new(),
810            vec![
811                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
812                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from(" ")))),
813                ColumnarValue::Scalar(ScalarValue::Int64(Some(-1))),
814            ],
815            Ok(Some("a,b")),
816            &str,
817            Utf8,
818            StringArray
819        );
820        test_function!(
821            SplitPartFunc::new(),
822            vec![
823                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("a,b")))),
824                ColumnarValue::Scalar(ScalarValue::Utf8(Some(String::from("")))),
825                ColumnarValue::Scalar(ScalarValue::Int64(Some(-2))),
826            ],
827            Ok(Some("")),
828            &str,
829            Utf8,
830            StringArray
831        );
832
833        Ok(())
834    }
835
836    #[test]
837    fn test_split_part_stringview_sliced() -> Result<()> {
838        use super::split_part_scalar_view;
839
840        let strings: StringViewArray = vec![
841            Some("skip_this.value"),
842            Some("this_is_a_long_prefix.suffix"),
843            Some("short.val"),
844            Some("another_long_result.rest"),
845            None,
846        ]
847        .into_iter()
848        .collect();
849
850        // Slice off the first element to get a non-zero offset array.
851        let sliced = strings.slice(1, 4);
852        let result = split_part_scalar_view(&sliced, ".", 1)?;
853        let result = result.as_string_view();
854        assert_eq!(result.len(), 4);
855        assert_eq!(result.value(0), "this_is_a_long_prefix");
856        assert_eq!(result.value(1), "short");
857        assert_eq!(result.value(2), "another_long_result");
858        assert!(result.is_null(3));
859
860        Ok(())
861    }
862}