-
Notifications
You must be signed in to change notification settings - Fork 829
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Specialize Prefix/Suffix Match for Like/ILike
between Array and Scalar for StringViewArray
#6231
Changes from all commits
894e797
a80431f
e4ad6d9
32d6dc2
f6f8f55
6bee687
3322905
cd5886f
7eb3c83
4a27f96
0c9ac9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -989,6 +989,27 @@ mod tests { | |
vec![false, true, true, false, false, false, false, true, true, true, true] | ||
); | ||
|
||
// 😈 is four bytes long. | ||
test_utf8_scalar!( | ||
test_uff8_array_like_multibyte, | ||
vec![ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 it occurs to me we should also be testing with |
||
"sdlkdfFooßsdfs", | ||
"sdlkdfFooSSdggs", | ||
"sdlkdfFoosssdsd", | ||
"FooS", | ||
"Foos", | ||
"ffooSS", | ||
"ffooß", | ||
"😃sadlksffofsSsh😈klF", | ||
"😱slgffoesSsh😈klF", | ||
"FFKoSS", | ||
"longer than 12 bytes FFKoSS", | ||
], | ||
"%Ssh😈klF", | ||
like, | ||
vec![false, false, false, false, false, false, false, true, true, false, false] | ||
); | ||
|
||
test_utf8_scalar!( | ||
test_utf8_array_ilike_scalar_one, | ||
vec![ | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -15,7 +15,7 @@ | |||||||||||||||||||||||
// specific language governing permissions and limitations | ||||||||||||||||||||||||
// under the License. | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
use arrow_array::{ArrayAccessor, BooleanArray}; | ||||||||||||||||||||||||
use arrow_array::{ArrayAccessor, BooleanArray, StringViewArray}; | ||||||||||||||||||||||||
use arrow_schema::ArrowError; | ||||||||||||||||||||||||
use memchr::memchr2; | ||||||||||||||||||||||||
use memchr::memmem::Finder; | ||||||||||||||||||||||||
|
@@ -114,28 +114,103 @@ impl<'a> Predicate<'a> { | |||||||||||||||||||||||
Predicate::IEqAscii(v) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
haystack.eq_ignore_ascii_case(v) != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::Contains(finder) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
finder.find(haystack.as_bytes()).is_some() != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::StartsWith(v) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
starts_with(haystack, v, equals_kernel) != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::IStartsWithAscii(v) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
starts_with(haystack, v, equals_ignore_ascii_case_kernel) != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::EndsWith(v) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
ends_with(haystack, v, equals_kernel) != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::IEndsWithAscii(v) => BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
ends_with(haystack, v, equals_ignore_ascii_case_kernel) != negate | ||||||||||||||||||||||||
}), | ||||||||||||||||||||||||
Predicate::Contains(finder) => { | ||||||||||||||||||||||||
if let Some(string_view_array) = array.as_any().downcast_ref::<StringViewArray>() { | ||||||||||||||||||||||||
BooleanArray::from( | ||||||||||||||||||||||||
string_view_array | ||||||||||||||||||||||||
.bytes_iter() | ||||||||||||||||||||||||
.map(|haystack| finder.find(haystack).is_some() != negate) | ||||||||||||||||||||||||
.collect::<Vec<_>>(), | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking more carefully at arrow-rs/arrow-array/src/array/byte_view_array.rs Lines 547 to 557 in 7eb3c83
It isn't clear to me that how calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the test should be we'll try benchmarking and see if this improves things There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ya, I thought the only differences between For
For
There are merely differences between the indexing operations and iterator methods. The benchmark also indicates in %5 ranges. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made a PR here to refine this documentation: #6306 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another difference is that |
||||||||||||||||||||||||
finder.find(haystack.as_bytes()).is_some() != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Predicate::StartsWith(v) => { | ||||||||||||||||||||||||
if let Some(string_view_array) = array.as_any().downcast_ref::<StringViewArray>() { | ||||||||||||||||||||||||
BooleanArray::from( | ||||||||||||||||||||||||
string_view_array | ||||||||||||||||||||||||
alamb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
.prefix_bytes_iter(v.len()) | ||||||||||||||||||||||||
.map(|haystack| { | ||||||||||||||||||||||||
equals_bytes(haystack, v.as_bytes(), equals_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
.collect::<Vec<_>>(), | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
starts_with(haystack, v, equals_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Predicate::IStartsWithAscii(v) => { | ||||||||||||||||||||||||
if let Some(string_view_array) = array.as_any().downcast_ref::<StringViewArray>() { | ||||||||||||||||||||||||
BooleanArray::from( | ||||||||||||||||||||||||
string_view_array | ||||||||||||||||||||||||
.prefix_bytes_iter(v.len()) | ||||||||||||||||||||||||
.map(|haystack| { | ||||||||||||||||||||||||
equals_bytes( | ||||||||||||||||||||||||
haystack, | ||||||||||||||||||||||||
v.as_bytes(), | ||||||||||||||||||||||||
equals_ignore_ascii_case_kernel, | ||||||||||||||||||||||||
) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
.collect::<Vec<_>>(), | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
starts_with(haystack, v, equals_ignore_ascii_case_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Predicate::EndsWith(v) => { | ||||||||||||||||||||||||
if let Some(string_view_array) = array.as_any().downcast_ref::<StringViewArray>() { | ||||||||||||||||||||||||
BooleanArray::from( | ||||||||||||||||||||||||
string_view_array | ||||||||||||||||||||||||
.suffix_bytes_iter(v.len()) | ||||||||||||||||||||||||
.map(|haystack| { | ||||||||||||||||||||||||
equals_bytes(haystack, v.as_bytes(), equals_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
.collect::<Vec<_>>(), | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
ends_with(haystack, v, equals_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Predicate::IEndsWithAscii(v) => { | ||||||||||||||||||||||||
if let Some(string_view_array) = array.as_any().downcast_ref::<StringViewArray>() { | ||||||||||||||||||||||||
BooleanArray::from( | ||||||||||||||||||||||||
string_view_array | ||||||||||||||||||||||||
.suffix_bytes_iter(v.len()) | ||||||||||||||||||||||||
.map(|haystack| { | ||||||||||||||||||||||||
equals_bytes( | ||||||||||||||||||||||||
haystack, | ||||||||||||||||||||||||
v.as_bytes(), | ||||||||||||||||||||||||
equals_ignore_ascii_case_kernel, | ||||||||||||||||||||||||
) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
.collect::<Vec<_>>(), | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| { | ||||||||||||||||||||||||
ends_with(haystack, v, equals_ignore_ascii_case_kernel) != negate | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Predicate::Regex(v) => { | ||||||||||||||||||||||||
BooleanArray::from_unary(array, |haystack| v.is_match(haystack) != negate) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
fn equals_bytes(lhs: &[u8], rhs: &[u8], byte_eq_kernel: impl Fn((&u8, &u8)) -> bool) -> bool { | ||||||||||||||||||||||||
lhs.len() == rhs.len() && zip(lhs, rhs).all(byte_eq_kernel) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// This is faster than `str::starts_with` for small strings. | ||||||||||||||||||||||||
/// See <https://github.com/apache/arrow-rs/issues/6107> for more details. | ||||||||||||||||||||||||
fn starts_with(haystack: &str, needle: &str, byte_eq_kernel: impl Fn((&u8, &u8)) -> bool) -> bool { | ||||||||||||||||||||||||
|
@@ -145,7 +220,6 @@ fn starts_with(haystack: &str, needle: &str, byte_eq_kernel: impl Fn((&u8, &u8)) | |||||||||||||||||||||||
zip(haystack.as_bytes(), needle.as_bytes()).all(byte_eq_kernel) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// This is faster than `str::ends_with` for small strings. | ||||||||||||||||||||||||
/// See <https://github.com/apache/arrow-rs/issues/6107> for more details. | ||||||||||||||||||||||||
fn ends_with(haystack: &str, needle: &str, byte_eq_kernel: impl Fn((&u8, &u8)) -> bool) -> bool { | ||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as you mentioned above, having to return an empty slice just for the function to immediate check it again might be another potential performance improvement
What do you think about making this more general and take a function? Maybe something like the following (untested)
I am not sure this is a good idea but figured maybe it would be more general. But maybe not...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought passing a function pointer to the
*_iters
was a bad decision. I did this actually in the first version of this PR, e.g.,This was good, but a circular on the crate dependencies was introduced, i.e.,
This could be solved by re-layouting the code but lots of changes there.
Also, the functions are very specialized, as they should not be. The function signature is not flexible enough to generalize all such requirements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense -- thank you for the explanation. Let's keep exploring this method for now