aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzverevgeny <zverevgeny@yandex-team.com>2025-04-25 13:47:32 +0300
committerzverevgeny <zverevgeny@yandex-team.com>2025-04-25 14:34:55 +0300
commitcecedbe2b29df37e09409fc2d15fb8211269d8b1 (patch)
tree5a03c7812ce3b54e22244e8a2740930f03bdf232
parenteadbc0700cb45e9d5706f48f9a233c1b6016433d (diff)
downloadydb-cecedbe2b29df37e09409fc2d15fb8211269d8b1.tar.gz
Primitives for case insensitive simple pattern match
commit_hash:5f4bdb090c2f60459073e3e95ccd39ec58b95232
-rw-r--r--yql/essentials/core/expr_nodes/yql_expr_nodes.json21
-rw-r--r--yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp67
-rw-r--r--yql/essentials/core/type_ann/type_ann_core.cpp18
-rw-r--r--yql/essentials/sql/v1/context.cpp1
-rw-r--r--yql/essentials/sql/v1/context.h1
-rw-r--r--yql/essentials/sql/v1/node.cpp5
-rw-r--r--yql/essentials/sql/v1/sql_expression.cpp20
-rw-r--r--yql/essentials/sql/v1/sql_query.cpp6
-rw-r--r--yql/essentials/tests/sql/minirun/part5/canondata/result.json14
-rw-r--r--yql/essentials/tests/sql/minirun/part6/canondata/result.json14
-rw-r--r--yql/essentials/tests/sql/sql2yql/canondata/result.json12
-rw-r--r--yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_like-ilike_/formatted.sql51
-rw-r--r--yql/essentials/tests/sql/suites/like/ilike-Ansi.cfg1
-rw-r--r--yql/essentials/tests/sql/suites/like/ilike.cfg0
-rw-r--r--yql/essentials/tests/sql/suites/like/ilike.sql48
15 files changed, 266 insertions, 13 deletions
diff --git a/yql/essentials/core/expr_nodes/yql_expr_nodes.json b/yql/essentials/core/expr_nodes/yql_expr_nodes.json
index c051517eee9..e0b08030e94 100644
--- a/yql/essentials/core/expr_nodes/yql_expr_nodes.json
+++ b/yql/essentials/core/expr_nodes/yql_expr_nodes.json
@@ -1281,6 +1281,11 @@
"Match": {"Type": "Callable", "Name": "=="}
},
{
+ "Name": "TCoCmpEqualsIgnoreCase",
+ "Base": "TCoCompare",
+ "Match": {"Type": "Callable", "Name": "EqualsIgnoreCase"}
+ },
+ {
"Name": "TCoCmpNotEqual",
"Base": "TCoCompare",
"Match": {"Type": "Callable", "Name": "!="}
@@ -1301,16 +1306,32 @@
"Match": {"Type": "Callable", "Name": "StartsWith"}
},
{
+ "Name": "TCoCmpStartsWithIgnoreCase",
+ "Base": "TCoCompare",
+ "Match": {"Type": "Callable", "Name": "StartsWithIgnoreCase"}
+ },
+ {
"Name": "TCoCmpEndsWith",
"Base": "TCoCompare",
"Match": {"Type": "Callable", "Name": "EndsWith"}
},
{
+ "Name": "TCoCmpEndsWithIgnoreCase",
+ "Base": "TCoCompare",
+ "Match": {"Type": "Callable", "Name": "EndsWithIgnoreCase"}
+ },
+
+ {
"Name": "TCoCmpStringContains",
"Base": "TCoCompare",
"Match": {"Type": "Callable", "Name": "StringContains"}
},
{
+ "Name": "TCoCmpStringContainsIgnoreCase",
+ "Base": "TCoCompare",
+ "Match": {"Type": "Callable", "Name": "StringContainsIgnoreCase"}
+ },
+ {
"Name": "TCoInc",
"Base": "TCallable",
"Match": {"Type": "Callable", "Name": "Inc"},
diff --git a/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp b/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp
index ef78839b26d..2b64e4e0bd1 100644
--- a/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp
+++ b/yql/essentials/core/peephole_opt/yql_opt_peephole_physical.cpp
@@ -8620,6 +8620,69 @@ TExprNode::TPtr ExpandSqlCompare(const TExprNode::TPtr& node, TExprContext& ctx)
return node;
}
+TExprNode::TPtr ExpandContainsIgnoreCase(const TExprNode::TPtr& node, TExprContext& ctx) {
+ YQL_CLOG(DEBUG, CorePeepHole) << "Expand " << node->Content();
+ const TString part{node->Child(1)->Child(0)->Content()};
+ TString pattern;
+ if (node->Content() == "EqualsIgnoreCase") {
+ pattern = part;
+ } else if (node->Content() == "StartsWithIgnoreCase") {
+ pattern = part + "%";
+ } else if (node->Content() == "EndsWithIgnoreCase") {
+ pattern = "%" + part;
+ } else if (node->Content() == "StringContainsIgnoreCase") {
+ pattern = "%" + part + "%";
+ } else {
+ YQL_ENSURE(!"Unknown IngoreCase node");
+ }
+ const auto pos = node->Pos();
+ auto patternExpr = ctx.Builder(pos)
+ .Callable("Apply")
+ .Callable(0, "Udf")
+ .Atom(0, "Re2.PatternFromLike")
+ .Seal()
+ .Callable(1, node->Child(1)->Content())
+ .Atom(0, pattern)
+ .Seal()
+ .Seal()
+ .Build();
+
+
+ auto optionsExpr = ctx.Builder(pos)
+ .Callable("NamedApply")
+ .Callable(0, "Udf")
+ .Atom(0, "Re2.Options")
+ .Seal()
+ .List(1)
+ .Seal()
+ .Callable(2, "AsStruct")
+ .List(0)
+ .Atom(0, "CaseSensitive")
+ .Callable(1, "Bool")
+ .Atom(0, "false", TNodeFlags::Default)
+ .Seal()
+ .Seal()
+ .Seal()
+ .Seal()
+ .Build();
+
+ auto result = ctx.Builder(pos)
+ .Callable("Apply")
+ .Callable(0, "AssumeStrict")
+ .Callable(0, "Udf")
+ .Atom(0, "Re2.Match")
+ .List(1)
+ .Add(0, patternExpr)
+ .Add(1, optionsExpr)
+ .Seal()
+ .Seal()
+ .Seal()
+ .Add(1, node->Child(0))
+ .Seal()
+ .Build();
+
+ return result;
+}
template <bool Equals>
TExprNode::TPtr ExpandAggrEqual(const TExprNode::TPtr& node, TExprContext& ctx) {
@@ -8944,6 +9007,10 @@ struct TPeepHoleRules {
{"EmptyIterator", &DropDependsOnFromEmptyIterator},
{"Version", &ExpandVersion},
{RightName, &ExpandRightOverCons},
+ {"EqualsIgnoreCase", &ExpandContainsIgnoreCase},
+ {"StartsWithIgnoreCase", &ExpandContainsIgnoreCase},
+ {"EndsWithIgnoreCase", &ExpandContainsIgnoreCase},
+ {"StringContainsIgnoreCase", &ExpandContainsIgnoreCase},
};
const TExtPeepHoleOptimizerMap CommonStageExtRules = {
diff --git a/yql/essentials/core/type_ann/type_ann_core.cpp b/yql/essentials/core/type_ann/type_ann_core.cpp
index a4a780a71af..e97b1690c4a 100644
--- a/yql/essentials/core/type_ann/type_ann_core.cpp
+++ b/yql/essentials/core/type_ann/type_ann_core.cpp
@@ -3323,7 +3323,9 @@ namespace NTypeAnnImpl {
return IGraphTransformer::TStatus::Error;
}
- if (IsNull(input->Head()) || IsNull(input->Tail())) {
+ const auto ignoreNulls = input->Content().ends_with("IgnoreCase");
+
+ if ((IsNull(input->Head()) || IsNull(input->Tail())) && !ignoreNulls) {
output = MakeBoolNothing(input->Pos(), ctx.Expr);
return IGraphTransformer::TStatus::Repeat;
}
@@ -3343,8 +3345,11 @@ namespace NTypeAnnImpl {
}
bool isOptional = false;
const TDataExprType* dataType = nullptr;
- if (!IsDataOrOptionalOfData(type, isOptional, dataType) ||
- !(dataType->GetSlot() == EDataSlot::String || dataType->GetSlot() == EDataSlot::Utf8))
+ if ((!IsDataOrOptionalOfData(type, isOptional, dataType) ||
+ !(dataType->GetSlot() == EDataSlot::String || dataType->GetSlot() == EDataSlot::Utf8) ||
+ dataType->IsOptionalOrNull()) &&
+ (!IsNull(*type) && ignoreNulls)
+ )
{
ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(child->Pos()), TStringBuilder()
<< "Expected (optional) string/utf8 or corresponding Pg type, but got: " << *child->GetTypeAnn()));
@@ -3352,8 +3357,7 @@ namespace NTypeAnnImpl {
}
hasOptionals = hasOptionals || isOptional;
}
-
- if (hasOptionals)
+ if (hasOptionals && !ignoreNulls)
input->SetTypeAnn(ctx.Expr.MakeType<TOptionalExprType>(ctx.Expr.MakeType<TDataExprType>(EDataSlot::Bool)));
else
input->SetTypeAnn(ctx.Expr.MakeType<TDataExprType>(EDataSlot::Bool));
@@ -12490,6 +12494,7 @@ template <NKikimr::NUdf::EDataSlot DataSlot>
Functions["GreaterOrEqual"] = &CompareWrapper<false>;
Functions["=="] = &CompareWrapper<true>;
Functions["Equal"] = &CompareWrapper<true>;
+ Functions["EqualsIgnoreCase"] = &WithWrapper;
Functions["!="] = &CompareWrapper<true>;
Functions["NotEqual"] = &CompareWrapper<true>;
Functions["Inc"] = &IncDecWrapper<true>;
@@ -12553,8 +12558,11 @@ template <NKikimr::NUdf::EDataSlot DataSlot>
Functions["Find"] = &FindWrapper;
Functions["RFind"] = &FindWrapper;
Functions["StartsWith"] = &WithWrapper;
+ Functions["StartsWithIgnoreCase"] = &WithWrapper;
Functions["EndsWith"] = &WithWrapper;
+ Functions["EndsWithIgnoreCase"] = &WithWrapper;
Functions["StringContains"] = &WithWrapper;
+ Functions["StringContainsIgnoreCase"] = &WithWrapper;
Functions["ByteAt"] = &ByteAtWrapper;
Functions["ListIf"] = &ListIfWrapper;
Functions["AsList"] = &AsListWrapper<false>;
diff --git a/yql/essentials/sql/v1/context.cpp b/yql/essentials/sql/v1/context.cpp
index ae313a171e1..9decaefd4e9 100644
--- a/yql/essentials/sql/v1/context.cpp
+++ b/yql/essentials/sql/v1/context.cpp
@@ -71,6 +71,7 @@ THashMap<TStringBuf, TPragmaField> CTX_PRAGMA_FIELDS = {
{"DistinctOverKeys", &TContext::DistinctOverKeys},
{"GroupByExprAfterWhere", &TContext::GroupByExprAfterWhere},
{"FailOnGroupByExprOverride", &TContext::FailOnGroupByExprOverride},
+ {"OptimizeSimpleILIKE", &TContext::OptimizeSimpleIlike}
};
typedef TMaybe<bool> TContext::*TPragmaMaybeField;
diff --git a/yql/essentials/sql/v1/context.h b/yql/essentials/sql/v1/context.h
index 14314c0a314..e45a0495a2c 100644
--- a/yql/essentials/sql/v1/context.h
+++ b/yql/essentials/sql/v1/context.h
@@ -376,6 +376,7 @@ namespace NSQLTranslationV1 {
bool GroupByExprAfterWhere = false;
bool FailOnGroupByExprOverride = false;
bool EmitUnionMerge = false;
+ bool OptimizeSimpleIlike = false;
TVector<size_t> ForAllStatementsParts;
TMaybe<TString> Engine;
diff --git a/yql/essentials/sql/v1/node.cpp b/yql/essentials/sql/v1/node.cpp
index e5c7d353795..fe5d4dff7b0 100644
--- a/yql/essentials/sql/v1/node.cpp
+++ b/yql/essentials/sql/v1/node.cpp
@@ -3179,7 +3179,10 @@ TNodePtr BuildBinaryOp(TContext& ctx, TPosition pos, const TString& opName, TNod
return nullptr;
}
- static const THashSet<TStringBuf> nullSafeOps = {"IsDistinctFrom", "IsNotDistinctFrom"};
+ static const THashSet<TStringBuf> nullSafeOps = {
+ "IsDistinctFrom", "IsNotDistinctFrom",
+ "EqualsIgnoreCase", "StartsWithIgnoreCase", "EndsWithIgnoreCase", "StringContainsIgnoreCase"
+ };
if (!nullSafeOps.contains(opName)) {
const bool bothArgNull = a->IsNull() && b->IsNull();
const bool oneArgNull = a->IsNull() || b->IsNull();
diff --git a/yql/essentials/sql/v1/sql_expression.cpp b/yql/essentials/sql/v1/sql_expression.cpp
index 3eb1904cc59..4b7e31fdaa3 100644
--- a/yql/essentials/sql/v1/sql_expression.cpp
+++ b/yql/essentials/sql/v1/sql_expression.cpp
@@ -1797,19 +1797,25 @@ TNodePtr TSqlExpression::SubExpr(const TRule_xor_subexpr& node, const TTrailingQ
return nullptr;
}
- if (opName == "like" || mayIgnoreCase) {
+ if ((opName == "like") || mayIgnoreCase || Ctx.OptimizeSimpleIlike) {
// TODO: expand LIKE in optimizers - we can analyze argument types there
+ const bool useIgnoreCaseOp = (opName == "ilike") && !mayIgnoreCase;
+ const auto& equalOp = useIgnoreCaseOp ? "EqualsIgnoreCase" : "==";
+ const auto& startsWithOp = useIgnoreCaseOp ? "StartsWithIgnoreCase" : "StartsWith";
+ const auto& endsWithOp = useIgnoreCaseOp ? "EndsWithIgnoreCase" : "EndsWith";
+ const auto& containsOp = useIgnoreCaseOp ? "StringContainsIgnoreCase" : "StringContains";
+
YQL_ENSURE(!components.empty());
const auto& first = components.front();
if (components.size() == 1 && first.IsSimple) {
// no '%'s and '_'s in pattern
YQL_ENSURE(first.Prefix == first.Suffix);
- isMatch = BuildBinaryOp(Ctx, pos, "==", res, BuildLiteralRawString(pos, first.Suffix, isUtf8));
+ isMatch = BuildBinaryOp(Ctx, pos, equalOp, res, BuildLiteralRawString(pos, first.Suffix, isUtf8));
} else if (!first.Prefix.empty()) {
const TString& prefix = first.Prefix;
TNodePtr prefixMatch;
if (Ctx.EmitStartsWith) {
- prefixMatch = BuildBinaryOp(Ctx, pos, "StartsWith", res, BuildLiteralRawString(pos, prefix, isUtf8));
+ prefixMatch = BuildBinaryOp(Ctx, pos, startsWithOp, res, BuildLiteralRawString(pos, prefix, isUtf8));
} else {
prefixMatch = BuildBinaryOp(Ctx, pos, ">=", res, BuildLiteralRawString(pos, prefix, isUtf8));
auto upperBound = isUtf8 ? NextValidUtf8(prefix) : NextLexicographicString(prefix);
@@ -1834,7 +1840,7 @@ TNodePtr TSqlExpression::SubExpr(const TRule_xor_subexpr& node, const TTrailingQ
TNodePtr sizePred = BuildBinaryOp(Ctx, pos, ">=",
TNodePtr(new TCallNodeImpl(pos, "Size", { res })),
TNodePtr(new TLiteralNumberNode<ui32>(pos, "Uint32", ToString(prefix.size() + suffix.size()))));
- TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, suffix, isUtf8));
+ TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, endsWithOp, res, BuildLiteralRawString(pos, suffix, isUtf8));
isMatch = new TCallNodeImpl(pos, "And", {
sizePred,
prefixMatch,
@@ -1849,14 +1855,14 @@ TNodePtr TSqlExpression::SubExpr(const TRule_xor_subexpr& node, const TTrailingQ
if (components.size() == 3 && components.back().Prefix.empty()) {
// '%foo%'
YQL_ENSURE(!components[1].Prefix.empty());
- isMatch = BuildBinaryOp(Ctx, pos, "StringContains", res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
+ isMatch = BuildBinaryOp(Ctx, pos, containsOp, res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
} else if (components.size() == 2) {
// '%foo'
- isMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
+ isMatch = BuildBinaryOp(Ctx, pos, endsWithOp, res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
}
} else if (Ctx.AnsiLike && !components.back().Suffix.empty()) {
const TString& suffix = components.back().Suffix;
- TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, suffix, isUtf8));
+ TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, endsWithOp, res, BuildLiteralRawString(pos, suffix, isUtf8));
isMatch = BuildBinaryOp(Ctx, pos, "And", suffixMatch, isMatch);
}
// TODO: more StringContains/StartsWith/EndsWith cases?
diff --git a/yql/essentials/sql/v1/sql_query.cpp b/yql/essentials/sql/v1/sql_query.cpp
index c9ea302c8c7..a750d941917 100644
--- a/yql/essentials/sql/v1/sql_query.cpp
+++ b/yql/essentials/sql/v1/sql_query.cpp
@@ -3434,6 +3434,12 @@ TNodePtr TSqlQuery::PragmaStatement(const TRule_pragma_stmt& stmt, bool& success
}
Ctx.Engine = *literal;
+ } else if (normalizedPragma == "optimizesimpleilike") {
+ Ctx.OptimizeSimpleIlike = true;
+ Ctx.IncrementMonCounter("sql_pragma", "OptimizeSimpleILIKE");
+ } else if (normalizedPragma == "disableoptimizesimpleilike") {
+ Ctx.OptimizeSimpleIlike = false;
+ Ctx.IncrementMonCounter("sql_pragma", "DisableOptimizeSimpleILIKE");
} else {
Error() << "Unknown pragma: " << pragma;
Ctx.IncrementMonCounter("sql_errors", "UnknownPragma");
diff --git a/yql/essentials/tests/sql/minirun/part5/canondata/result.json b/yql/essentials/tests/sql/minirun/part5/canondata/result.json
index ec17a6eec0b..1eadc4bc263 100644
--- a/yql/essentials/tests/sql/minirun/part5/canondata/result.json
+++ b/yql/essentials/tests/sql/minirun/part5/canondata/result.json
@@ -1145,6 +1145,20 @@
"uri": "https://{canondata_backend}/1942100/1466d7e49a6dc5a8df761a5ac92539095e1a14a0/resource.tar.gz#test.test_library-library_udf--Results_/results.txt"
}
],
+ "test.test[like-ilike--Debug]": [
+ {
+ "checksum": "e9edc47e022f94844d6755223a748bbe",
+ "size": 5224,
+ "uri": "https://{canondata_backend}/1899731/c18b136d437186ca17b897aa3cbd89e03d6ab70e/resource.tar.gz#test.test_like-ilike--Debug_/opt.yql"
+ }
+ ],
+ "test.test[like-ilike--Results]": [
+ {
+ "checksum": "4f140efd3491b22880e294dce1c735c2",
+ "size": 9773,
+ "uri": "https://{canondata_backend}/1899731/c18b136d437186ca17b897aa3cbd89e03d6ab70e/resource.tar.gz#test.test_like-ilike--Results_/results.txt"
+ }
+ ],
"test.test[like-like_escape-default.txt-Debug]": [
{
"checksum": "9a241374a2f6995712f675e514d7e64a",
diff --git a/yql/essentials/tests/sql/minirun/part6/canondata/result.json b/yql/essentials/tests/sql/minirun/part6/canondata/result.json
index 60e9fee9459..74fb98a7cc1 100644
--- a/yql/essentials/tests/sql/minirun/part6/canondata/result.json
+++ b/yql/essentials/tests/sql/minirun/part6/canondata/result.json
@@ -875,6 +875,20 @@
"uri": "file://test.test_lambda-list_aggregate_flatmap-default.txt-Results_/extracted"
}
],
+ "test.test[like-ilike-Ansi-Debug]": [
+ {
+ "checksum": "2bf3a064a80a62b0d1e57b4fc19f85aa",
+ "size": 4198,
+ "uri": "https://{canondata_backend}/1871102/cf0bf303bf8ddaa5f80dc41d0b1079fd931793f8/resource.tar.gz#test.test_like-ilike-Ansi-Debug_/opt.yql"
+ }
+ ],
+ "test.test[like-ilike-Ansi-Results]": [
+ {
+ "checksum": "4f140efd3491b22880e294dce1c735c2",
+ "size": 9773,
+ "uri": "https://{canondata_backend}/1871102/cf0bf303bf8ddaa5f80dc41d0b1079fd931793f8/resource.tar.gz#test.test_like-ilike-Ansi-Results_/results.txt"
+ }
+ ],
"test.test[match_recognize-alerts_without_order-default.txt-Debug]": [
{
"checksum": "617ad997b55cab0792da8c20f2fdeb07",
diff --git a/yql/essentials/tests/sql/sql2yql/canondata/result.json b/yql/essentials/tests/sql/sql2yql/canondata/result.json
index dd80cad3ba4..7bb69811dcd 100644
--- a/yql/essentials/tests/sql/sql2yql/canondata/result.json
+++ b/yql/essentials/tests/sql/sql2yql/canondata/result.json
@@ -4283,6 +4283,13 @@
"uri": "https://{canondata_backend}/1942173/99e88108149e222741552e7e6cddef041d6a2846/resource.tar.gz#test_sql2yql.test_library-library_yqls_/sql.yql"
}
],
+ "test_sql2yql.test[like-ilike]": [
+ {
+ "checksum": "82ed5458d953d02cb6b225495713cd99",
+ "size": 11180,
+ "uri": "https://{canondata_backend}/1946324/f3be5399e4ba264ff20e7048ebc764e7239129dc/resource.tar.gz#test_sql2yql.test_like-ilike_/sql.yql"
+ }
+ ],
"test_sql2yql.test[like-like_escape]": [
{
"checksum": "dad96e3616b966f1a15c37b4396213a1",
@@ -10521,6 +10528,11 @@
"uri": "file://test_sql_format.test_library-library_yqls_/formatted.sql"
}
],
+ "test_sql_format.test[like-ilike]": [
+ {
+ "uri": "file://test_sql_format.test_like-ilike_/formatted.sql"
+ }
+ ],
"test_sql_format.test[like-like_escape]": [
{
"uri": "file://test_sql_format.test_like-like_escape_/formatted.sql"
diff --git a/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_like-ilike_/formatted.sql b/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_like-ilike_/formatted.sql
new file mode 100644
index 00000000000..96e89141855
--- /dev/null
+++ b/yql/essentials/tests/sql/sql2yql/canondata/test_sql_format.test_like-ilike_/formatted.sql
@@ -0,0 +1,51 @@
+PRAGMA OptimizeSimpleILIKE;
+
+SELECT
+ 'test' ILIKE 'test',
+ 'TEST' ILIKE 'test',
+ 'тест' ILIKE 'ТЕСТ',
+ 'Привет' ILIKE 'пРиВеТ',
+ 'prefix' ILIKE 'Pre%',
+ 'префикс' ILIKE 'Пре%',
+ 'suFfix' ILIKE '%Fix',
+ 'СУФФИКС' ILIKE '%Икс',
+ 'инфикс' ILIKE '%Фи%',
+ 'Комплекс' ILIKE '%О%П%С',
+
+ --negative
+ 'тест' ILIKE 'Тесть',
+ 'Тост' ILIKE '%мост',
+ 'Лось' ILIKE 'Лом%'
+;
+
+SELECT
+ NULL ILIKE 'test',
+ NULL ILIKE 'te%',
+ NULL ILIKE '%st',
+ NULL ILIKE '%es%'
+;
+
+$table = [
+ <|
+ str: 'TeSt',
+ optStr: Just('TeSt'),
+ nullStr: Nothing(String?)
+ |>
+];
+
+SELECT
+ str ILIKE 'test',
+ str ILIKE 'te%',
+ str ILIKE '%st',
+ str ILIKE '%es%',
+ optStr ILIKE 'test',
+ optStr ILIKE 'te%',
+ optStr ILIKE '%st',
+ optStr ILIKE '%es%',
+ nullStr ILIKE 'test',
+ nullStr ILIKE 'te%',
+ nullStr ILIKE '%st',
+ nullStr ILIKE '%es%'
+FROM
+ AS_TABLE($table)
+;
diff --git a/yql/essentials/tests/sql/suites/like/ilike-Ansi.cfg b/yql/essentials/tests/sql/suites/like/ilike-Ansi.cfg
new file mode 100644
index 00000000000..7dc25ec8499
--- /dev/null
+++ b/yql/essentials/tests/sql/suites/like/ilike-Ansi.cfg
@@ -0,0 +1 @@
+pragma AnsiLike \ No newline at end of file
diff --git a/yql/essentials/tests/sql/suites/like/ilike.cfg b/yql/essentials/tests/sql/suites/like/ilike.cfg
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/yql/essentials/tests/sql/suites/like/ilike.cfg
diff --git a/yql/essentials/tests/sql/suites/like/ilike.sql b/yql/essentials/tests/sql/suites/like/ilike.sql
new file mode 100644
index 00000000000..b67825e2feb
--- /dev/null
+++ b/yql/essentials/tests/sql/suites/like/ilike.sql
@@ -0,0 +1,48 @@
+PRAGMA OptimizeSimpleILIKE;
+
+SELECT
+ 'test' ILIKE 'test',
+ 'TEST' ILIKE 'test',
+ 'тест' ILIKE 'ТЕСТ',
+ 'Привет' ILIKE 'пРиВеТ',
+ 'prefix' ILIKE 'Pre%',
+ 'префикс' ILIKE 'Пре%',
+ 'suFfix' ILIKE '%Fix',
+ 'СУФФИКС' ILIKE '%Икс',
+ 'инфикс' ILIKE '%Фи%',
+ 'Комплекс' ILIKE '%О%П%С',
+ --negative
+ 'тест' ILIKE 'Тесть',
+ 'Тост' ILIKE '%мост',
+ 'Лось' ILIKE 'Лом%'
+;
+
+SELECT
+ Null ILIKE 'test',
+ Null ILIKE 'te%',
+ Null ILIKE '%st',
+ Null ILIKE '%es%'
+;
+
+$table = [<|
+ str: 'TeSt',
+ optStr: Just("TeSt"),
+ nullStr: Nothing(String?)
+|>];
+
+SELECT
+ str ILIKE 'test',
+ str ILIKE 'te%',
+ str ILIKE '%st',
+ str ILIKE '%es%',
+
+ optStr ILIKE 'test',
+ optStr ILIKE 'te%',
+ optStr ILIKE '%st',
+ optStr ILIKE '%es%',
+
+ nullStr ILIKE 'test',
+ nullStr ILIKE 'te%',
+ nullStr ILIKE '%st',
+ nullStr ILIKE '%es%'
+FROM AS_TABLE($table); \ No newline at end of file