Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 38 additions & 25 deletions datafusion/functions/src/unicode/substrindex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use std::any::Any;
use std::sync::Arc;

use arrow::array::{
ArrayAccessor, ArrayIter, ArrayRef, ArrowPrimitiveType, AsArray, OffsetSizeTrait,
PrimitiveArray, StringBuilder,
ArrayAccessor, ArrayIter, ArrayRef, ArrowPrimitiveType, AsArray, GenericStringBuilder, OffsetSizeTrait, PrimitiveArray, StringBuilder
};
use arrow::datatypes::{DataType, Int32Type, Int64Type};

Expand Down Expand Up @@ -182,7 +181,8 @@ fn substr_index_general<
where
T::Native: OffsetSizeTrait,
{
let mut builder = StringBuilder::new();
let num_rows = string_array.len();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted to make this really fast, you could also implement special case code for the common cases where delimiter and count were scalar values -- I suspect you could make it quite fast.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I’ll try adding a special-case implementation for that. Thanks!

let mut builder = GenericStringBuilder::<T::Native>::with_capacity(num_rows, 0);
let string_iter = ArrayIter::new(string_array);
let delimiter_array_iter = ArrayIter::new(delimiter_array);
let count_array_iter = ArrayIter::new(count_array);
Expand All @@ -198,31 +198,44 @@ where
}

let occurrences = usize::try_from(n.unsigned_abs()).unwrap_or(usize::MAX);
let length = if n > 0 {
let split = string.split(delimiter);
split
.take(occurrences)
.map(|s| s.len() + delimiter.len())
.sum::<usize>()
- delimiter.len()
} else {
let split = string.rsplit(delimiter);
split
.take(occurrences)
.map(|s| s.len() + delimiter.len())
.sum::<usize>()
- delimiter.len()
};
if n > 0 {
match string.get(..length) {
Some(substring) => builder.append_value(substring),
None => builder.append_null(),
let result_idx = if delimiter.len() == 1 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked that this checks for length in bytes

https://doc.rust-lang.org/std/string/struct.String.html#method.len

let d_byte = delimiter.as_bytes()[0];
let bytes = string.as_bytes();

if n > 0 {
bytes.iter()
.enumerate()
.filter(|&(_, &b)| b == d_byte)
.nth(occurrences - 1)
.map(|(idx, _)| idx)
} else {
bytes.iter()
.enumerate()
.rev()
.filter(|&(_, &b)| b == d_byte)
.nth(occurrences - 1)
.map(|(idx, _)| idx + 1)
}
} else {
match string.get(string.len().saturating_sub(length)..) {
Some(substring) => builder.append_value(substring),
None => builder.append_null(),
if n > 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this else { if .. } block can be collapsed

string.match_indices(delimiter)
.nth(occurrences - 1)
.map(|(idx, _)| idx)
} else {
string.rmatch_indices(delimiter)
.nth(occurrences - 1)
.map(|(idx, _)| idx + delimiter.len())
}
};
match result_idx {
Some(idx) => {
if n > 0 {
builder.append_value(&string[..idx]);
} else {
builder.append_value(&string[idx..]);
}
}
None => builder.append_value(string),
}
}
_ => builder.append_null(),
Expand Down