Skip to content

Commit c230726

Browse files
authored
Convert like and ilike to sqlfunc macro (#34342)
Converts the like and ilike operators to the sqlfunc macros. Part of converting all binary functions to generated scaffolding. --------- Signed-off-by: Moritz Hoffmann <[email protected]>
1 parent 65ebfd7 commit c230726

File tree

8 files changed

+245
-94
lines changed

8 files changed

+245
-94
lines changed

src/expr-derive/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
/// `output_type`.
3737
/// * `could_error`: A boolean indicating whether the function could error.
3838
/// * `propagate_nulls`: A boolean indicating whether the function propagates nulls. Applies to
39-
/// binary functions only.
39+
/// binary functions only. If not specified, use the default implementation from the trait.
40+
/// The default is to return true if all input types are not-nullable.
4041
/// * `introduces_nulls`: A boolean indicating whether the function introduces nulls. Applies to
41-
/// all functions.
42+
/// all functions. If not specified, use the default implementation from the trait.
43+
/// The default is to return the `nullable` property of the output type.
4244
///
4345
/// # Limitations
4446
/// * The input and output types can contain lifetime parameters, as long as they are `'a`.

src/expr/src/scalar.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,11 +782,25 @@ impl MirScalarExpr {
782782
Err(err.clone()),
783783
e.typ(column_types).scalar_type,
784784
);
785-
} else if let BinaryFunc::IsLikeMatch { case_insensitive } = func {
785+
} else if let BinaryFunc::IsLikeMatchCaseInsensitive(_) = func {
786786
if expr2.is_literal() {
787787
// We can at least precompile the regex.
788788
let pattern = expr2.as_literal_str().unwrap();
789-
*e = match like_pattern::compile(pattern, *case_insensitive) {
789+
*e = match like_pattern::compile(pattern, true) {
790+
Ok(matcher) => expr1.take().call_unary(UnaryFunc::IsLikeMatch(
791+
func::IsLikeMatch(matcher),
792+
)),
793+
Err(err) => MirScalarExpr::literal(
794+
Err(err),
795+
e.typ(column_types).scalar_type,
796+
),
797+
};
798+
}
799+
} else if let BinaryFunc::IsLikeMatchCaseSensitive(_) = func {
800+
if expr2.is_literal() {
801+
// We can at least precompile the regex.
802+
let pattern = expr2.as_literal_str().unwrap();
803+
*e = match like_pattern::compile(pattern, false) {
790804
Ok(matcher) => expr1.take().call_unary(UnaryFunc::IsLikeMatch(
791805
func::IsLikeMatch(matcher),
792806
)),

src/expr/src/scalar/func.rs

Lines changed: 83 additions & 80 deletions
Large diffs are not rendered by default.

src/expr/src/scalar/func/binary.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,8 @@ mod derive {
273273
GetByte,
274274
Gt,
275275
Gte,
276-
// IsLikeMatch
276+
IsLikeMatchCaseInsensitive,
277+
IsLikeMatchCaseSensitive,
277278
// IsRegexpMatch
278279
JsonbConcat,
279280
JsonbContainsJsonb,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
source: src/expr/src/scalar/func.rs
3+
expression: "#[sqlfunc(is_infix_op = true, sqlname = \"ilike\")]\nfn is_like_match_case_insensitive(\n haystack: &str,\n pattern: &str,\n) -> Result<bool, EvalError> {\n like_pattern::compile(pattern, true).map(|needle| needle.is_match(haystack))\n}\n"
4+
---
5+
#[derive(
6+
proptest_derive::Arbitrary,
7+
Ord,
8+
PartialOrd,
9+
Clone,
10+
Debug,
11+
Eq,
12+
PartialEq,
13+
serde::Serialize,
14+
serde::Deserialize,
15+
Hash,
16+
mz_lowertest::MzReflect
17+
)]
18+
pub struct IsLikeMatchCaseInsensitive;
19+
impl<'a> crate::func::binary::EagerBinaryFunc<'a> for IsLikeMatchCaseInsensitive {
20+
type Input1 = &'a str;
21+
type Input2 = &'a str;
22+
type Output = Result<bool, EvalError>;
23+
fn call(
24+
&self,
25+
a: Self::Input1,
26+
b: Self::Input2,
27+
temp_storage: &'a mz_repr::RowArena,
28+
) -> Self::Output {
29+
is_like_match_case_insensitive(a, b)
30+
}
31+
fn output_type(
32+
&self,
33+
input_type_a: mz_repr::SqlColumnType,
34+
input_type_b: mz_repr::SqlColumnType,
35+
) -> mz_repr::SqlColumnType {
36+
use mz_repr::AsColumnType;
37+
let output = Self::Output::as_column_type();
38+
let propagates_nulls = crate::func::binary::EagerBinaryFunc::propagates_nulls(
39+
self,
40+
);
41+
let nullable = output.nullable;
42+
output
43+
.nullable(
44+
nullable
45+
|| (propagates_nulls
46+
&& (input_type_a.nullable || input_type_b.nullable)),
47+
)
48+
}
49+
fn is_infix_op(&self) -> bool {
50+
true
51+
}
52+
}
53+
impl std::fmt::Display for IsLikeMatchCaseInsensitive {
54+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55+
f.write_str("ilike")
56+
}
57+
}
58+
fn is_like_match_case_insensitive(
59+
haystack: &str,
60+
pattern: &str,
61+
) -> Result<bool, EvalError> {
62+
like_pattern::compile(pattern, true).map(|needle| needle.is_match(haystack))
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
source: src/expr/src/scalar/func.rs
3+
expression: "#[sqlfunc(is_infix_op = true, sqlname = \"like\")]\nfn is_like_match_case_sensitive(\n haystack: &str,\n pattern: &str,\n) -> Result<bool, EvalError> {\n like_pattern::compile(pattern, false).map(|needle| needle.is_match(haystack))\n}\n"
4+
---
5+
#[derive(
6+
proptest_derive::Arbitrary,
7+
Ord,
8+
PartialOrd,
9+
Clone,
10+
Debug,
11+
Eq,
12+
PartialEq,
13+
serde::Serialize,
14+
serde::Deserialize,
15+
Hash,
16+
mz_lowertest::MzReflect
17+
)]
18+
pub struct IsLikeMatchCaseSensitive;
19+
impl<'a> crate::func::binary::EagerBinaryFunc<'a> for IsLikeMatchCaseSensitive {
20+
type Input1 = &'a str;
21+
type Input2 = &'a str;
22+
type Output = Result<bool, EvalError>;
23+
fn call(
24+
&self,
25+
a: Self::Input1,
26+
b: Self::Input2,
27+
temp_storage: &'a mz_repr::RowArena,
28+
) -> Self::Output {
29+
is_like_match_case_sensitive(a, b)
30+
}
31+
fn output_type(
32+
&self,
33+
input_type_a: mz_repr::SqlColumnType,
34+
input_type_b: mz_repr::SqlColumnType,
35+
) -> mz_repr::SqlColumnType {
36+
use mz_repr::AsColumnType;
37+
let output = Self::Output::as_column_type();
38+
let propagates_nulls = crate::func::binary::EagerBinaryFunc::propagates_nulls(
39+
self,
40+
);
41+
let nullable = output.nullable;
42+
output
43+
.nullable(
44+
nullable
45+
|| (propagates_nulls
46+
&& (input_type_a.nullable || input_type_b.nullable)),
47+
)
48+
}
49+
fn is_infix_op(&self) -> bool {
50+
true
51+
}
52+
}
53+
impl std::fmt::Display for IsLikeMatchCaseSensitive {
54+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55+
f.write_str("like")
56+
}
57+
}
58+
fn is_like_match_case_sensitive(
59+
haystack: &str,
60+
pattern: &str,
61+
) -> Result<bool, EvalError> {
62+
like_pattern::compile(pattern, false).map(|needle| needle.is_match(haystack))
63+
}

src/sql/src/func.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4646,24 +4646,24 @@ pub static OP_IMPLS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
46464646

46474647
// ILIKE
46484648
"~~*" => Scalar {
4649-
params!(String, String) => BF::IsLikeMatch { case_insensitive: true } => Bool, 1627;
4649+
params!(String, String) => BF::from(func::IsLikeMatchCaseInsensitive) => Bool, 1627;
46504650
params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
46514651
let length = ecx.scalar_type(&lhs).unwrap_char_length();
46524652
Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
4653-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: true })
4653+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
46544654
)
46554655
}) => Bool, 1629;
46564656
},
46574657
"!~~*" => Scalar {
46584658
params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
46594659
Ok(lhs
4660-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: true })
4660+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
46614661
.call_unary(UnaryFunc::Not(func::Not)))
46624662
}) => Bool, 1628;
46634663
params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
46644664
let length = ecx.scalar_type(&lhs).unwrap_char_length();
46654665
Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
4666-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: true })
4666+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
46674667
.call_unary(UnaryFunc::Not(func::Not))
46684668
)
46694669
}) => Bool, 1630;
@@ -4672,24 +4672,24 @@ pub static OP_IMPLS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
46724672

46734673
// LIKE
46744674
"~~" => Scalar {
4675-
params!(String, String) => BF::IsLikeMatch { case_insensitive: false } => Bool, 1209;
4675+
params!(String, String) => BF::from(func::IsLikeMatchCaseSensitive) => Bool, 1209;
46764676
params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
46774677
let length = ecx.scalar_type(&lhs).unwrap_char_length();
46784678
Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
4679-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: false })
4679+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
46804680
)
46814681
}) => Bool, 1211;
46824682
},
46834683
"!~~" => Scalar {
46844684
params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
46854685
Ok(lhs
4686-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: false })
4686+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
46874687
.call_unary(UnaryFunc::Not(func::Not)))
46884688
}) => Bool, 1210;
46894689
params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
46904690
let length = ecx.scalar_type(&lhs).unwrap_char_length();
46914691
Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
4692-
.call_binary(rhs, BF::IsLikeMatch { case_insensitive: false })
4692+
.call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
46934693
.call_unary(UnaryFunc::Not(func::Not))
46944694
)
46954695
}) => Bool, 1212;

src/sql/src/plan/query.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4490,7 +4490,12 @@ fn plan_like(
44904490
expr_func::LikeEscape,
44914491
);
44924492
}
4493-
let like = haystack.call_binary(pattern, BinaryFunc::IsLikeMatch { case_insensitive });
4493+
let func: BinaryFunc = if case_insensitive {
4494+
expr_func::IsLikeMatchCaseInsensitive.into()
4495+
} else {
4496+
expr_func::IsLikeMatchCaseSensitive.into()
4497+
};
4498+
let like = haystack.call_binary(pattern, func);
44944499
if not {
44954500
Ok(like.call_unary(UnaryFunc::Not(expr_func::Not)))
44964501
} else {

0 commit comments

Comments
 (0)