Skip to content
Merged
Changes from all commits
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
59 changes: 55 additions & 4 deletions arrow-string/src/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
// specific language governing permissions and limitations
// under the License.

//! Provide SQL's LIKE operators for Arrow's string arrays
//! String predicate kernels for Arrow arrays.
//!
//! Provides SQL `LIKE`/`ILIKE` kernels as well as related
//! string predicates such as `contains`, `starts_with`, `ends_with`, and
//! ASCII case-insensitive equality.

use crate::predicate::Predicate;

Expand All @@ -34,6 +38,7 @@ pub(crate) enum Op {
Like(bool),
ILike(bool),
Contains,
EqIgnoreAsciiCase,
StartsWith,
EndsWith,
}
Expand All @@ -46,6 +51,7 @@ impl std::fmt::Display for Op {
Op::ILike(false) => write!(f, "ILIKE"),
Op::ILike(true) => write!(f, "NILIKE"),
Op::Contains => write!(f, "CONTAINS"),
Op::EqIgnoreAsciiCase => write!(f, "EQ_IGNORE_ASCII_CASE"),
Op::StartsWith => write!(f, "STARTS_WITH"),
Op::EndsWith => write!(f, "ENDS_WITH"),
}
Expand Down Expand Up @@ -124,7 +130,7 @@ pub fn nilike(left: &dyn Datum, right: &dyn Datum) -> Result<BooleanArray, Arrow
/// # Example
/// ```
/// # use arrow_array::{StringArray, BooleanArray};
/// # use arrow_string::like::{like, starts_with};
/// # use arrow_string::like::starts_with;
/// let strings = StringArray::from(vec!["arrow-rs", "arrow-rs", "arrow-rs", "Parquet"]);
/// let patterns = StringArray::from(vec!["arr", "arrow", "arrow-cpp", "p"]);
///
Expand All @@ -150,7 +156,7 @@ pub fn starts_with(left: &dyn Datum, right: &dyn Datum) -> Result<BooleanArray,
/// # Example
/// ```
/// # use arrow_array::{StringArray, BooleanArray};
/// # use arrow_string::like::{ends_with, like, starts_with};
/// # use arrow_string::like::ends_with;
/// let strings = StringArray::from(vec!["arrow-rs", "arrow-rs", "Parquet"]);
/// let patterns = StringArray::from(vec!["arr", "-rs", "t"]);
///
Expand All @@ -176,7 +182,7 @@ pub fn ends_with(left: &dyn Datum, right: &dyn Datum) -> Result<BooleanArray, Ar
/// # Example
/// ```
/// # use arrow_array::{StringArray, BooleanArray};
/// # use arrow_string::like::{contains, like, starts_with};
/// # use arrow_string::like::contains;
/// let strings = StringArray::from(vec!["arrow-rs", "arrow-rs", "arrow-rs", "Parquet"]);
/// let patterns = StringArray::from(vec!["arr", "-rs", "arrow-cpp", "X"]);
///
Expand All @@ -187,6 +193,30 @@ pub fn contains(left: &dyn Datum, right: &dyn Datum) -> Result<BooleanArray, Arr
like_op(Op::Contains, left, right)
}

/// Perform equality check on two arrays using an ASCII case-insensitive match.
///
/// `left` and `right` must be the same type, and one of
/// - Utf8
/// - LargeUtf8
/// - Utf8View
///
/// # Example
/// ```
/// # use arrow_array::{StringArray, BooleanArray};
/// # use arrow_string::like::eq_ignore_ascii_case;
/// let strings = StringArray::from(vec!["arrow", "rs", "arrow-rS", "Parquet"]);
/// let patterns = StringArray::from(vec!["ARROW", "rS", "ARROW-rs", "arrow"]);
///
/// let result = eq_ignore_ascii_case(&strings, &patterns).unwrap();
/// assert_eq!(result, BooleanArray::from(vec![true, true, true, false]));
/// ```
pub fn eq_ignore_ascii_case(

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.

I'm hoping like is the correct module to add this function?

I added it here because I wanted to reuse a lot of the machinery that was already in place to invoke the Predicate, but I was hesitating because this does not have an equivalent SQL function, unlike the other functions/Operator variants in this module.

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 think this is fine

The other potential option would be in the eq module of arrow-ord -- but that doesn't have string stuff, so I think this is actually better

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.

Maybe we can change the module doc to be more general:

from

  //! Provide SQL's LIKE operators for Arrow's string arrays

to something like:

  //! String predicate kernels for Arrow arrays.
  //!
  //! Provides SQL `LIKE`/`ILIKE` kernels as well as related
  //! string predicates such as `contains`, `starts_with`, `ends_with`, and
  //! ASCII case-insensitive equality.

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.

yes, updated in 1f3b03c

left: &dyn Datum,
right: &dyn Datum,
) -> Result<BooleanArray, ArrowError> {
like_op(Op::EqIgnoreAsciiCase, left, right)
}

fn like_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) -> Result<BooleanArray, ArrowError> {
use arrow_schema::DataType::*;
let (l, l_s) = lhs.get();
Expand Down Expand Up @@ -328,6 +358,7 @@ fn op_scalar<'a, T: StringArrayType<'a>>(
Op::Like(neg) => Predicate::like(r)?.evaluate_array(l, neg),
Op::ILike(neg) => Predicate::ilike(r, l.is_ascii())?.evaluate_array(l, neg),
Op::Contains => Predicate::contains(r).evaluate_array(l, false),
Op::EqIgnoreAsciiCase => Predicate::IEqAscii(r).evaluate_array(l, false),
Op::StartsWith => Predicate::StartsWith(r).evaluate_array(l, false),
Op::EndsWith => Predicate::EndsWith(r).evaluate_array(l, false),
};
Expand Down Expand Up @@ -362,6 +393,10 @@ fn op_binary<'a>(
Op::Like(neg) => binary_predicate(l, r, neg, Predicate::like),
Op::ILike(neg) => binary_predicate(l, r, neg, |s| Predicate::ilike(s, false)),
Op::Contains => Ok(l.zip(r).map(|(l, r)| Some(str_contains(l?, r?))).collect()),
Op::EqIgnoreAsciiCase => Ok(l
.zip(r)
.map(|(l, r)| Some(Predicate::IEqAscii(l?).evaluate(r?)))
.collect()),
Op::StartsWith => Ok(l
.zip(r)
.map(|(l, r)| Some(Predicate::StartsWith(r?).evaluate(l?)))
Expand Down Expand Up @@ -1394,6 +1429,22 @@ mod tests {
vec![true, false, true, true, true]
);

test_utf8!(
test_utf8_array_eq_ignore_ascii_case,
vec!["arrow", "arrow", "arrow", "arrow", "parquet", "parquet"],
vec!["arrow", "ARROW", "arro", "aRrOw", "arrow", "ARROW"],
eq_ignore_ascii_case,
vec![true, true, false, true, false, false]
);

test_utf8_scalar!(
test_utf8_array_eq_ignore_ascii_case_scalar,
vec!["arrow", "aRrOW", "arro", "ARROW", "parquet", "PARQUET"],
"arrow",
eq_ignore_ascii_case,
vec![true, true, false, true, false, false]
);

#[test]
fn test_dict_like_kernels() {
let data = vec![
Expand Down
Loading