diff --git a/datafusion/functions/src/unicode/common.rs b/datafusion/functions/src/unicode/common.rs index 9f91e4f1b0a2b..5dc8f334da8a3 100644 --- a/datafusion/functions/src/unicode/common.rs +++ b/datafusion/functions/src/unicode/common.rs @@ -125,16 +125,22 @@ pub(crate) enum StringCharLen { /// Calculate the byte length of the substring of `n` chars from string `string` #[inline] fn left_right_byte_length(string: &str, n: i64) -> usize { + let abs = n.unsigned_abs().min(usize::MAX as u64) as usize; + // For ASCII input every character is exactly one byte, so the byte offset of + // the n-th codepoint is just the (clamped) character count. This avoids the + // per-character `char_indices()` scan of the general path. match n.cmp(&0) { + Ordering::Equal => 0, + // `abs` chars trimmed from the end: keep the leading `len - abs`. + Ordering::Less if string.is_ascii() => string.len().saturating_sub(abs), Ordering::Less => string .char_indices() - .nth_back((n.unsigned_abs().min(usize::MAX as u64) - 1) as usize) + .nth_back(abs - 1) .map(|(index, _)| index) .unwrap_or(0), - Ordering::Equal => 0, - Ordering::Greater => { - byte_offset_of_char(string, n.unsigned_abs().min(usize::MAX as u64) as usize) - } + // First `abs` chars, but never past the end of the string. + Ordering::Greater if string.is_ascii() => abs.min(string.len()), + Ordering::Greater => byte_offset_of_char(string, abs), } }