This is an automated email from the ASF dual-hosted git repository.
snuyanzin pushed a commit to branch release-1.20
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/release-1.20 by this push:
new 58ffbb0f0f5 [FLINK-39424][table] Setting LIKE does not support default
escape characters
58ffbb0f0f5 is described below
commit 58ffbb0f0f5be719472d5a4be58b01c7556406f2
Author: Sergey Nuyanzin <[email protected]>
AuthorDate: Thu Apr 16 18:34:27 2026 +0200
[FLINK-39424][table] Setting LIKE does not support default escape characters
---
.../apache/flink/table/functions/SqlLikeUtils.java | 12 ++-
.../table/planner/codegen/calls/LikeCallGen.scala | 3 +
.../planner/calcite/FlinkSqlLikeUtilsTest.java | 30 ++++++++
.../planner/functions/LikeFunctionITCase.java | 6 +-
.../planner/expressions/ScalarFunctionsTest.scala | 88 ++++++++++++++++++++--
5 files changed, 129 insertions(+), 10 deletions(-)
diff --git
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/functions/SqlLikeUtils.java
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/functions/SqlLikeUtils.java
index c414f349b6e..380fe972cd7 100644
---
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/functions/SqlLikeUtils.java
+++
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/functions/SqlLikeUtils.java
@@ -96,6 +96,9 @@ public class SqlLikeUtils {
throw invalidEscapeCharacter(escapeStr.toString());
}
escapeChar = escapeStr.charAt(0);
+ if (escapeChar == 0) {
+ throw invalidEscapeCharacter(escapeStr.toString());
+ }
} else {
escapeChar = 0;
}
@@ -112,7 +115,7 @@ public class SqlLikeUtils {
if (JAVA_REGEX_SPECIALS.indexOf(c) >= 0) {
javaPattern.append('\\');
}
- if (c == escapeChar) {
+ if (c == escapeChar && escapeChar != 0) {
if (i == (sqlPattern.length() - 1)) {
throw invalidEscapeSequence(sqlPattern, i);
}
@@ -186,7 +189,7 @@ public class SqlLikeUtils {
char c = sqlPattern.charAt(i);
if (c == ']') {
return i - 1;
- } else if (c == escapeChar) {
+ } else if (c == escapeChar && escapeChar != 0) {
i++;
char nextChar = sqlPattern.charAt(i);
if (SQL_SIMILAR_SPECIALS.indexOf(nextChar) >= 0) {
@@ -235,6 +238,9 @@ public class SqlLikeUtils {
throw invalidEscapeCharacter(escapeStr.toString());
}
escapeChar = escapeStr.charAt(0);
+ if (escapeChar == 0) {
+ throw invalidEscapeCharacter(escapeStr.toString());
+ }
} else {
escapeChar = 0;
}
@@ -250,7 +256,7 @@ public class SqlLikeUtils {
final int len = sqlPattern.length();
for (int i = 0; i < len; i++) {
char c = sqlPattern.charAt(i);
- if (c == escapeChar) {
+ if (c == escapeChar && escapeChar != 0) {
if (i == (len - 1)) {
// It should never reach here after the escape rule
// checking.
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/LikeCallGen.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/LikeCallGen.scala
index 7d05ad13d02..f734389b629 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/LikeCallGen.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/LikeCallGen.scala
@@ -65,6 +65,9 @@ class LikeCallGen extends CallGenerator {
throw SqlLikeUtils.invalidEscapeCharacter(escape)
}
val escapeChar = escape.charAt(escape.length - 1)
+ if (escapeChar == 0) {
+ throw SqlLikeUtils.invalidEscapeCharacter(escape)
+ }
var matched = true
var i = 0
val newBuilder = new StringBuilder
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkSqlLikeUtilsTest.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkSqlLikeUtilsTest.java
index 9a55dec5b13..6bc784b0638 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkSqlLikeUtilsTest.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkSqlLikeUtilsTest.java
@@ -23,6 +23,7 @@ import org.apache.flink.table.functions.SqlLikeUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** Test for the SqlLikeUtils. */
class FlinkSqlLikeUtilsTest {
@@ -33,8 +34,37 @@ class FlinkSqlLikeUtilsTest {
assertThat(SqlLikeUtils.like("abcd", "a.*d", "\\")).isEqualTo(false);
assertThat(SqlLikeUtils.like("abcde", "%c.e", "\\")).isEqualTo(false);
+ // no default escape character - backslash is treated as a literal
character
+ assertThat(SqlLikeUtils.like("a-c", "a\\_c")).isEqualTo(false);
+ assertThat(SqlLikeUtils.like("a_c", "a\\_c")).isEqualTo(false);
+ assertThat(SqlLikeUtils.like("a\\_c", "a\\_c")).isEqualTo(true);
+
+ // default escape also excludes \u0000
+ assertThat(SqlLikeUtils.like("_", "\u0000_", null)).isEqualTo(false);
+ assertThat(SqlLikeUtils.like("\u0000x", "\u0000_",
null)).isEqualTo(true);
+
+ // -------------------------------- sqlToRegexLike
----------------------------------------
+
+ assertThat(SqlLikeUtils.sqlToRegexLike(".", "\\")).isEqualTo("\\.");
+ assertThat(SqlLikeUtils.sqlToRegexLike("c", "\\")).isEqualTo("c");
+ assertThat(SqlLikeUtils.sqlToRegexLike("_", "\\")).isEqualTo(".");
+ assertThat(SqlLikeUtils.sqlToRegexLike("%",
"\\")).isEqualTo("(?s:.*)");
+
+ // exception
+ assertThatThrownBy(() -> SqlLikeUtils.sqlToRegexLike("\\a", "\\"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessageContaining("Invalid escape");
+ assertThatThrownBy(() -> SqlLikeUtils.sqlToRegexLike("\\", "\\"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessageContaining("Invalid escape");
+
+ // --------------------------------- similar
----------------------------------------------
+
assertThat(SqlLikeUtils.similar("abc", "a.c", "\\")).isEqualTo(true);
assertThat(SqlLikeUtils.similar("a.c", "a.c", "\\")).isEqualTo(true);
assertThat(SqlLikeUtils.similar("abcd", "a.*d", "\\")).isEqualTo(true);
+ // default escape also excludes \u0000
+ assertThat(SqlLikeUtils.similar("_", "\u0000_",
null)).isEqualTo(false);
+ assertThat(SqlLikeUtils.similar("\u0000x", "\u0000_",
null)).isEqualTo(true);
}
}
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/LikeFunctionITCase.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/LikeFunctionITCase.java
index f2c29eae578..17981f0fd8c 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/LikeFunctionITCase.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/LikeFunctionITCase.java
@@ -204,6 +204,10 @@ class LikeFunctionITCase extends BuiltInFunctionTestBase {
.testSqlResult(
"'\btest\ne\\nd\f' LIKE '\btest\ne\\nd\f'
ESCAPE '!'",
true,
- DataTypes.BOOLEAN().notNull()));
+ DataTypes.BOOLEAN().notNull())
+ // Invalid escape character
+ .testSqlValidationError(
+ "f0 LIKE 'test' ESCAPE '\u0000'",
+ "Invalid escape character '\u0000'"));
}
}
diff --git
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
index 700f0138b97..975cdb32269 100644
---
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
+++
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
@@ -423,23 +423,99 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
@Test
def testLikeWithEscape(): Unit = {
testSqlApi("f23 LIKE '&%Th_s%' ESCAPE '&'", "TRUE")
-
testSqlApi("f23 LIKE '&%%is a%' ESCAPE '&'", "TRUE")
-
testSqlApi("f0 LIKE 'Th_s%' ESCAPE '&'", "TRUE")
-
testSqlApi("f0 LIKE '%is a%' ESCAPE '&'", "TRUE")
+
+ // normal escape character
+ testSqlApi("'TE-ST' LIKE '%E#_S%' ESCAPE '#'", "FALSE")
+ testSqlApi("'TE_ST' LIKE '%E#_S%' ESCAPE '#'", "TRUE")
+
+ // special character in SQL
+ testSqlApi("'TE-ST' LIKE '%E__S%' ESCAPE '_'", "FALSE")
+ testSqlApi("'TE_ST' LIKE '%E__S%' ESCAPE '_'", "TRUE")
+ testSqlApi("'TE-ST' LIKE 'TE%_ST' ESCAPE '%'", "FALSE")
+ testSqlApi("'TE_ST' LIKE 'TE%_ST' ESCAPE '%'", "TRUE")
+ testSqlApi("'TE-ST' LIKE '%E*_S%' ESCAPE '*'", "FALSE")
+ testSqlApi("'TE_ST' LIKE '%E*_S%' ESCAPE '*'", "TRUE")
+
+ // special character in Java Regex
+ testSqlApi("'TE-ST' LIKE '%E\\_S%' ESCAPE '\\'", "FALSE")
+ testSqlApi("'TE_ST' LIKE '%E\\_S%' ESCAPE '\\'", "TRUE")
+ testSqlApi("'TE-ST' LIKE '%E._S%' ESCAPE '.'", "FALSE")
+ testSqlApi("'TE_ST' LIKE '%E._S%' ESCAPE '.'", "TRUE")
+
+ // invalid escape character
+ testExpectedSqlException(
+ "'TE-ST' LIKE '%E_S%' ESCAPE 'ab'",
+ "Invalid escape",
+ classOf[RuntimeException])
+ testExpectedSqlException(
+ "'TE-ST' LIKE '%E_S%' ESCAPE '\\c'",
+ "Invalid escape",
+ classOf[RuntimeException])
+
+ // escape character at the end
+ testExpectedSqlException("'TE-ST' LIKE '%E_S%&' ESCAPE '&'", "",
classOf[RuntimeException])
+
+ // invalid character after escape character
+ testExpectedSqlException(
+ "'TE-ST' LIKE '%E&-S%' ESCAPE '&'",
+ "Invalid escape",
+ classOf[RuntimeException])
+ testExpectedSqlException(
+ "'TE-ST' LIKE '%E_S%' ESCAPE '_'",
+ "Invalid escape",
+ classOf[RuntimeException])
}
@Test
def testNotLikeWithEscape(): Unit = {
testSqlApi("f23 NOT LIKE '&%Th_s%' ESCAPE '&'", "FALSE")
-
testSqlApi("f23 NOT LIKE '&%%is a%' ESCAPE '&'", "FALSE")
-
testSqlApi("f0 NOT LIKE 'Th_s%' ESCAPE '&'", "FALSE")
-
testSqlApi("f0 NOT LIKE '%is a%' ESCAPE '&'", "FALSE")
+
+ // normal escape character
+ testSqlApi("'TE-ST' NOT LIKE '%E#_S%' ESCAPE '#'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE '%E#_S%' ESCAPE '#'", "FALSE")
+
+ // special character in SQL
+ testSqlApi("'TE-ST' NOT LIKE '%E__S%' ESCAPE '_'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE '%E__S%' ESCAPE '_'", "FALSE")
+ testSqlApi("'TE-ST' NOT LIKE 'TE%_ST' ESCAPE '%'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE 'TE%_ST' ESCAPE '%'", "FALSE")
+ testSqlApi("'TE-ST' NOT LIKE '%E*_S%' ESCAPE '*'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE '%E*_S%' ESCAPE '*'", "FALSE")
+
+ // special character in Java Regex
+ testSqlApi("'TE-ST' NOT LIKE '%E\\_S%' ESCAPE '\\'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE '%E\\_S%' ESCAPE '\\'", "FALSE")
+ testSqlApi("'TE-ST' NOT LIKE '%E._S%' ESCAPE '.'", "TRUE")
+ testSqlApi("'TE_ST' NOT LIKE '%E._S%' ESCAPE '.'", "FALSE")
+
+ // invalid character
+ testExpectedSqlException(
+ "'TE-ST' NOT LIKE '%E_S%' ESCAPE 'ab'",
+ "Invalid escape",
+ classOf[RuntimeException])
+ testExpectedSqlException(
+ "'TE-ST' NOT LIKE '%E_S%' ESCAPE '\\c'",
+ "Invalid escape",
+ classOf[RuntimeException])
+
+ // escape character at the end
+ testExpectedSqlException("'TE-ST' NOT LIKE '%E_S%&' ESCAPE '&'", "",
classOf[RuntimeException])
+
+ // invalid character after escape character
+ testExpectedSqlException(
+ "'TE-ST' NOT LIKE '%E&-S%' ESCAPE '&'",
+ "Invalid escape",
+ classOf[RuntimeException])
+ testExpectedSqlException(
+ "'TE-ST' NOT LIKE '%E_S%' ESCAPE '_'",
+ "Invalid escape",
+ classOf[RuntimeException])
}
@Test