From 304e943b343d92bd3849aeaaace51db93da80dc9 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt Date: Mon, 23 Jun 2025 18:35:51 +0800 Subject: [PATCH 1/5] support like with escape clause --- be/src/vec/functions/like.cpp | 60 ++++++++++++++++++- be/src/vec/functions/like.h | 5 +- .../org/apache/doris/nereids/DorisLexer.g4 | 1 + .../org/apache/doris/nereids/DorisParser.g4 | 4 +- .../nereids/parser/LogicalPlanBuilder.java | 14 +++-- .../expression/rules/LikeToEqualRewrite.java | 4 ++ .../doris/nereids/trees/expressions/Like.java | 46 +++++++++++++- .../string_functions/test_like_escape.out | 25 ++++++++ .../string_functions/test_like_escape.groovy | 43 +++++++++++++ 9 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out create mode 100644 regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy diff --git a/be/src/vec/functions/like.cpp b/be/src/vec/functions/like.cpp index 917752d82703f7..66f39c37a5340f 100644 --- a/be/src/vec/functions/like.cpp +++ b/be/src/vec/functions/like.cpp @@ -660,19 +660,58 @@ VPatternSearchStateSPtr FunctionLikeBase::pattern_type_recognition(const ColumnS } } +std::string replace_pattern_by_escape(const StringRef& pattern, char escape_char) { + std::string result; + result.reserve(pattern.size); + for (size_t i = 0; i < pattern.size; ++i) { + if (i + 1 < pattern.size && pattern.data[i] == escape_char && + (pattern.data[i + 1] == escape_char || pattern.data[i + 1] == '%' || + pattern.data[i + 1] == '_')) { + // "^^" -> "^" + // "^%" -> "\%" + // "^_" -> "\_" + if ((pattern.data[i + 1] == '%' || pattern.data[i + 1] == '_')) { + result.push_back('\\'); + } + result.push_back(pattern.data[i + 1]); + ++i; // skip next char + } else if (pattern.data[i] == '\\') { + // "\" -> "\\" + result.append("\\\\"); + } else { + result.push_back(pattern.data[i]); + } + } + return result; +} + Status FunctionLikeBase::vector_non_const(const ColumnString& values, const ColumnString& patterns, ColumnUInt8::Container& result, LikeState* state, size_t input_rows_count) const { + ColumnString::MutablePtr replaced_patterns; VPatternSearchStateSPtr vector_search_state; if (state->is_like_pattern) { - vector_search_state = pattern_type_recognition(patterns); + if (state->has_custom_escape) { + replaced_patterns = ColumnString::create(); + for (int i = 0; i < input_rows_count; ++i) { + std::string val = + replace_pattern_by_escape(patterns.get_data_at(i), state->escape_char); + replaced_patterns->insert_data(val.c_str(), val.size()); + } + vector_search_state = pattern_type_recognition(*replaced_patterns); + } else { + vector_search_state = pattern_type_recognition(patterns); + } } else { vector_search_state = pattern_type_recognition(patterns); } + + const ColumnString& real_pattern = state->has_custom_escape ? *replaced_patterns : patterns; + if (vector_search_state == nullptr) { // pattern type recognition failed, use default case for (int i = 0; i < input_rows_count; ++i) { - const auto pattern_val = patterns.get_data_at(i); + const auto pattern_val = real_pattern.get_data_at(i); const auto value_val = values.get_data_at(i); RETURN_IF_ERROR((state->scalar_function)(&state->search_state, value_val, pattern_val, &result[i])); @@ -809,7 +848,12 @@ void verbose_log_match(const std::string& str, const std::string& pattern_name, Status FunctionLike::construct_like_const_state(FunctionContext* context, const StringRef& pattern, std::shared_ptr& state, bool try_hyperscan) { - std::string pattern_str = pattern.to_string(); + std::string pattern_str; + if (state->has_custom_escape) { + pattern_str = replace_pattern_by_escape(pattern, state->escape_char); + } else { + pattern_str = pattern.to_string(); + } state->search_state.pattern_str = pattern_str; std::string search_string; @@ -914,6 +958,16 @@ Status FunctionLike::open(FunctionContext* context, FunctionContext::FunctionSta state->is_like_pattern = true; state->function = like_fn; state->scalar_function = like_fn_scalar; + if (context->is_col_constant(2)) { + state->has_custom_escape = true; + const auto escape_col = context->get_constant_col(2)->column_ptr; + const auto& escape = escape_col->get_data_at(0); + if (escape.size != 1) { + return Status::InternalError("Escape character must be a single character, got: {}", + escape.to_string()); + } + state->escape_char = escape.data[0]; + } if (context->is_col_constant(1)) { const auto pattern_col = context->get_constant_col(1)->column_ptr; const auto& pattern = pattern_col->get_data_at(0); diff --git a/be/src/vec/functions/like.h b/be/src/vec/functions/like.h index 3ec92b164ae3f7..cf205ee7a673f0 100644 --- a/be/src/vec/functions/like.h +++ b/be/src/vec/functions/like.h @@ -122,6 +122,8 @@ using VectorLikeFn = std::function; class FunctionLikeBase : public IFunction { public: - size_t get_number_of_arguments() const override { return 2; } + size_t get_number_of_arguments() const override { return 0; } + bool is_variadic() const override { return true; } DataTypePtr get_return_type_impl(const DataTypes& /*arguments*/) const override { return std::make_shared(); diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 index c9f0fdf976b297..96cb27fb86e868 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 @@ -229,6 +229,7 @@ ENGINE: 'ENGINE'; ENGINES: 'ENGINES'; ENTER: 'ENTER'; ERRORS: 'ERRORS'; +ESCAPE: 'ESCAPE'; EVENTS: 'EVENTS'; EVERY: 'EVERY'; EXCEPT: 'EXCEPT'; diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 7d0a50b8acce96..308fe3f6cddbb3 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -1515,7 +1515,8 @@ rowConstructorItem predicate : NOT? kind=BETWEEN lower=valueExpression AND upper=valueExpression - | NOT? kind=(LIKE | REGEXP | RLIKE) pattern=valueExpression + | NOT? kind=(REGEXP | RLIKE) pattern=valueExpression + | NOT? kind=LIKE pattern=valueExpression (ESCAPE escape=valueExpression)? | NOT? kind=(MATCH | MATCH_ANY | MATCH_ALL | MATCH_PHRASE | MATCH_PHRASE_PREFIX | MATCH_REGEXP | MATCH_PHRASE_EDGE) pattern=valueExpression | NOT? kind=IN LEFT_PAREN query RIGHT_PAREN | NOT? kind=IN LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN @@ -1903,6 +1904,7 @@ nonReserved | ENGINE | ENGINES | ERRORS + | ESCAPE | EVENTS | EVERY | EXCLUDE diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index c9c5eb6e313e00..e6d466cdec624a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -4213,10 +4213,16 @@ private Expression withPredicate(Expression valueExpression, PredicateContext ct } break; case DorisParser.LIKE: - outExpression = new Like( - valueExpression, - getExpression(ctx.pattern) - ); + if (ctx.ESCAPE() == null) { + outExpression = new Like( + valueExpression, + getExpression(ctx.pattern)); + } else { + outExpression = new Like( + valueExpression, + getExpression(ctx.pattern), + getExpression(ctx.escape)); + } break; case DorisParser.RLIKE: case DorisParser.REGEXP: diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java index 0e93b7ddc5a718..122aa7647bb726 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java @@ -44,6 +44,10 @@ public List> buildRules() { } private static Expression rewriteLikeToEqual(Like like) { + if (like.arity() == 3) { + return like; + } + Expression left = like.child(0); Expression right = like.child(1); if (!(right instanceof VarcharLiteral)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java index 84b6ffa984fff4..03a1fd3a679a55 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java @@ -17,7 +17,11 @@ package org.apache.doris.nereids.trees.expressions; +import org.apache.doris.catalog.FunctionSignature; +import org.apache.doris.nereids.exceptions.AnalysisException; import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.BooleanType; +import org.apache.doris.nereids.types.VarcharType; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -28,10 +32,20 @@ * like expression: a like 'xxx%'. */ public class Like extends StringRegexPredicate { + + private static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT, + VarcharType.SYSTEM_DEFAULT)); + public Like(Expression left, Expression right) { this(ImmutableList.of(left, right)); } + public Like(Expression left, Expression right, Expression escape) { + this(ImmutableList.of(left, right, escape)); + } + private Like(List children) { this(children, false); } @@ -40,9 +54,32 @@ private Like(List children, boolean inferred) { super("like", children, inferred); } + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public String computeToSql() { + if (arity() == 2) { + return super.computeToSql(); + } + return '(' + left().toSql() + ' ' + getName() + ' ' + right().toSql() + " ESCAPE " + child(2).toSql() + + ')'; + } + + @Override + public String toString() { + if (arity() == 2) { + return super.computeToSql(); + } + return "(" + left() + " " + getName() + " " + right() + " ESCAPE " + child(2) + + ")"; + } + @Override public Like withChildren(List children) { - Preconditions.checkArgument(children.size() == 2); + Preconditions.checkArgument(children.size() == 2 || children.size() == 3); return new Like(children); } @@ -54,4 +91,11 @@ public R accept(ExpressionVisitor visitor, C context) { public Expression withInferred(boolean inferred) { return new Like(this.children, inferred); } + + @Override + public void checkLegalityBeforeTypeCoercion() { + if (arity() == 3 && !child(2).isConstant()) { + throw new AnalysisException("like does not support non-constant escape character: " + this.toSql()); + } + } } diff --git a/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out b/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out new file mode 100644 index 00000000000000..8d2da31d9c7c4b --- /dev/null +++ b/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out @@ -0,0 +1,25 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !test -- +true + +-- !test -- +true + +-- !test -- +true + +-- !test -- +true + +-- !test -- +true + +-- !test -- +false + +-- !test -- +false + +-- !test -- +true + diff --git a/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy b/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy new file mode 100644 index 00000000000000..d31f9ae6eef77b --- /dev/null +++ b/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("test_like_escapes") { + qt_test """ + select "%a" like "a%_" ESCAPE "a"; + """ + qt_test """ + select "%_" like "a%_" ESCAPE "a"; + """ + qt_test """ + select "a" like "a" ESCAPE "a"; + """ + qt_test """ + select "a" like "aa" ESCAPE "a"; + """ + qt_test """ + select "%a" like "a%a" ESCAPE "a"; + """ + qt_test """ + select "%_" like "a%a" ESCAPE "a"; + """ + qt_test """ + select "%a" like "a%a_" ESCAPE "a"; + """ + qt_test """ + select "%_" like "a%a_" ESCAPE "a"; + """ +} \ No newline at end of file From 3a5436bc352b3e001f2af28fbb8e2ebd56b54b98 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt Date: Tue, 24 Jun 2025 10:30:00 +0800 Subject: [PATCH 2/5] update fe --- .../rules/expression/rules/LikeToEqualRewrite.java | 2 +- .../apache/doris/nereids/trees/expressions/Like.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java index 122aa7647bb726..69f3165ff2cd56 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java @@ -47,7 +47,7 @@ private static Expression rewriteLikeToEqual(Like like) { if (like.arity() == 3) { return like; } - + Expression left = like.child(0); Expression right = like.child(1); if (!(right instanceof VarcharLiteral)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java index 03a1fd3a679a55..c5e231db2255ac 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java @@ -19,6 +19,7 @@ import org.apache.doris.catalog.FunctionSignature; import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral; import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; import org.apache.doris.nereids.types.BooleanType; import org.apache.doris.nereids.types.VarcharType; @@ -94,8 +95,15 @@ public Expression withInferred(boolean inferred) { @Override public void checkLegalityBeforeTypeCoercion() { - if (arity() == 3 && !child(2).isConstant()) { - throw new AnalysisException("like does not support non-constant escape character: " + this.toSql()); + if (arity() == 3) { + if (child(2) instanceof StringLikeLiteral) { + String escapeChar = ((StringLikeLiteral) child(2)).getStringValue(); + if (escapeChar.length() != 1) { + throw new AnalysisException("like escape character must be a single character: " + escapeChar); + } + } else { + throw new AnalysisException("like escape character must be a string literal: " + this.toSql()); + } } } } From 5b96582251751be9a62818857619cfccc82a31d4 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt Date: Tue, 24 Jun 2025 14:13:39 +0800 Subject: [PATCH 3/5] update --- be/src/vec/functions/like.cpp | 25 ----------------- be/src/vec/functions/like.h | 25 +++++++++++++++++ be/test/vec/common/string_utils_test.cpp | 6 ++++ .../doris/nereids/trees/expressions/Like.java | 5 ++-- .../string_functions/test_like_escape.out | 15 ++++++++++ .../string_functions/test_like_escape.groovy | 28 +++++++++++++++++++ 6 files changed, 77 insertions(+), 27 deletions(-) diff --git a/be/src/vec/functions/like.cpp b/be/src/vec/functions/like.cpp index 66f39c37a5340f..44be303aad444e 100644 --- a/be/src/vec/functions/like.cpp +++ b/be/src/vec/functions/like.cpp @@ -660,31 +660,6 @@ VPatternSearchStateSPtr FunctionLikeBase::pattern_type_recognition(const ColumnS } } -std::string replace_pattern_by_escape(const StringRef& pattern, char escape_char) { - std::string result; - result.reserve(pattern.size); - for (size_t i = 0; i < pattern.size; ++i) { - if (i + 1 < pattern.size && pattern.data[i] == escape_char && - (pattern.data[i + 1] == escape_char || pattern.data[i + 1] == '%' || - pattern.data[i + 1] == '_')) { - // "^^" -> "^" - // "^%" -> "\%" - // "^_" -> "\_" - if ((pattern.data[i + 1] == '%' || pattern.data[i + 1] == '_')) { - result.push_back('\\'); - } - result.push_back(pattern.data[i + 1]); - ++i; // skip next char - } else if (pattern.data[i] == '\\') { - // "\" -> "\\" - result.append("\\\\"); - } else { - result.push_back(pattern.data[i]); - } - } - return result; -} - Status FunctionLikeBase::vector_non_const(const ColumnString& values, const ColumnString& patterns, ColumnUInt8::Container& result, LikeState* state, size_t input_rows_count) const { diff --git a/be/src/vec/functions/like.h b/be/src/vec/functions/like.h index cf205ee7a673f0..f0ab9f4968a134 100644 --- a/be/src/vec/functions/like.h +++ b/be/src/vec/functions/like.h @@ -50,6 +50,31 @@ class Block; namespace doris::vectorized { +inline std::string replace_pattern_by_escape(const StringRef& pattern, char escape_char) { + std::string result; + result.reserve(pattern.size); + for (size_t i = 0; i < pattern.size; ++i) { + if (i + 1 < pattern.size && pattern.data[i] == escape_char && + (pattern.data[i + 1] == escape_char || pattern.data[i + 1] == '%' || + pattern.data[i + 1] == '_')) { + // "^^" -> "^" + // "^%" -> "\%" + // "^_" -> "\_" + if ((pattern.data[i + 1] == '%' || pattern.data[i + 1] == '_')) { + result.push_back('\\'); + } + result.push_back(pattern.data[i + 1]); + ++i; // skip next char + } else if (pattern.data[i] == '\\') { + // "\" -> "\\" + result.append("\\\\"); + } else { + result.push_back(pattern.data[i]); + } + } + return result; +} + // TODO: replace with std::string_view when `LikeSearchState.substring_pattern` can // construct from std::string_view. struct LikeSearchState { diff --git a/be/test/vec/common/string_utils_test.cpp b/be/test/vec/common/string_utils_test.cpp index d3e58755b65a3a..d577bc4731ba92 100644 --- a/be/test/vec/common/string_utils_test.cpp +++ b/be/test/vec/common/string_utils_test.cpp @@ -19,6 +19,8 @@ #include +#include "vec/functions/like.h" + namespace doris::vectorized { class StringUtilsTest : public ::testing::Test { @@ -215,4 +217,8 @@ TEST_F(StringUtilsTest, TestIsValidIdentifierBegin) { } } +TEST_F(StringUtilsTest, replace_pattern_by_escape) { + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abcdef"}, 'A'), "abcdef"); +} + } // namespace doris::vectorized \ No newline at end of file diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java index c5e231db2255ac..4b51ec805e2835 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java @@ -98,8 +98,9 @@ public void checkLegalityBeforeTypeCoercion() { if (arity() == 3) { if (child(2) instanceof StringLikeLiteral) { String escapeChar = ((StringLikeLiteral) child(2)).getStringValue(); - if (escapeChar.length() != 1) { - throw new AnalysisException("like escape character must be a single character: " + escapeChar); + if (escapeChar.getBytes().length != 1) { + throw new AnalysisException( + "like escape character must be a single ascii character: " + escapeChar); } } else { throw new AnalysisException("like escape character must be a string literal: " + this.toSql()); diff --git a/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out b/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out index 8d2da31d9c7c4b..59f0007906ef3e 100644 --- a/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out +++ b/regression-test/data/query_p0/sql_functions/string_functions/test_like_escape.out @@ -23,3 +23,18 @@ false -- !test -- true +-- !test -- +true + +-- !test -- +false + +-- !test -- +true + +-- !test -- +false + +-- !test -- +true + diff --git a/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy b/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy index d31f9ae6eef77b..106d2709a850b7 100644 --- a/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy +++ b/regression-test/suites/query_p0/sql_functions/string_functions/test_like_escape.groovy @@ -40,4 +40,32 @@ suite("test_like_escapes") { qt_test """ select "%_" like "a%a_" ESCAPE "a"; """ + + test { + sql """select "啊啊" like "啊啊" ESCAPE "啊";""" + exception "like escape character must be a single ascii character" + } + test { + sql """select "a" like "aa" ESCAPE "aa";""" + exception "like escape character must be a single ascii character" + } + test { + sql """select "a" like "aa" ESCAPE 1;""" + exception "like escape character must be a string literal" + } + qt_test """ + select "啊%a" like "啊a%_" ESCAPE "a"; + """ + qt_test """ + select "%a" like "a%_" ESCAPE "A"; + """ + qt_test """ + select "\\\\" like "\\\\%" ESCAPE "A"; + """ + qt_test """ + select "\\\\" like "\\\\A%" ESCAPE "A"; + """ + qt_test """ + select "\\\\%" like "\\\\A%" ESCAPE "A"; + """ } \ No newline at end of file From d29f22f53821e401d8d6c5888edadf4f5656a29a Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt Date: Tue, 24 Jun 2025 14:22:26 +0800 Subject: [PATCH 4/5] update --- be/test/vec/common/string_utils_test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/be/test/vec/common/string_utils_test.cpp b/be/test/vec/common/string_utils_test.cpp index d577bc4731ba92..09c539358a80f9 100644 --- a/be/test/vec/common/string_utils_test.cpp +++ b/be/test/vec/common/string_utils_test.cpp @@ -219,6 +219,12 @@ TEST_F(StringUtilsTest, TestIsValidIdentifierBegin) { TEST_F(StringUtilsTest, replace_pattern_by_escape) { EXPECT_EQ(replace_pattern_by_escape(StringRef {"abcdef"}, 'A'), "abcdef"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abc^%def"}, '^'), "abc\\%def"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abc^^ef"}, '^'), "abc^ef"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abc^^^ef"}, '^'), "abc^^ef"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abc^^^_ef"}, '^'), "abc^\\_ef"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"abc^^^_^ef"}, '^'), "abc^\\_^ef"); + EXPECT_EQ(replace_pattern_by_escape(StringRef {"\\abc^^^_^ef"}, '^'), "\\\\abc^\\_^ef"); } } // namespace doris::vectorized \ No newline at end of file From 686f48734e59d7259c5de0f019dd90217247017a Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt Date: Thu, 26 Jun 2025 14:20:09 +0800 Subject: [PATCH 5/5] update --- .../rules/expression/rules/LikeToEqualRewrite.java | 8 ++++---- .../org/apache/doris/nereids/trees/expressions/Like.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java index 69f3165ff2cd56..a024f979faa7c6 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/LikeToEqualRewrite.java @@ -44,10 +44,6 @@ public List> buildRules() { } private static Expression rewriteLikeToEqual(Like like) { - if (like.arity() == 3) { - return like; - } - Expression left = like.child(0); Expression right = like.child(1); if (!(right instanceof VarcharLiteral)) { @@ -57,6 +53,10 @@ private static Expression rewriteLikeToEqual(Like like) { StringBuilder sb = new StringBuilder(); int len = str.length(); char escapeChar = '\\'; + if (like.arity() == 3) { + escapeChar = ((VarcharLiteral) like.child(2)).value.charAt(0); + } + for (int i = 0; i < len;) { char c = str.charAt(i); if (c == escapeChar && (i + 1) < len diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java index 4b51ec805e2835..10f25fb0ebca98 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Like.java @@ -65,7 +65,7 @@ public String computeToSql() { if (arity() == 2) { return super.computeToSql(); } - return '(' + left().toSql() + ' ' + getName() + ' ' + right().toSql() + " ESCAPE " + child(2).toSql() + return '(' + left().toSql() + ' ' + getName() + ' ' + right().toSql() + " escape " + child(2).toSql() + ')'; } @@ -74,7 +74,7 @@ public String toString() { if (arity() == 2) { return super.computeToSql(); } - return "(" + left() + " " + getName() + " " + right() + " ESCAPE " + child(2) + return "(" + left() + " " + getName() + " " + right() + " escape " + child(2) + ")"; }