Skip to content

Commit 176dee1

Browse files
snuyanzinAu-Minertwalthr
authored
[FLINK-39424][table] Setting LIKE does not support default escape characters
--------- Co-authored-by: Au-Miner <75485809+Au-Miner@users.noreply.github.com> Co-authored-by: Timo Walther <twalthr@apache.org>
1 parent bc0a0ea commit 176dee1

9 files changed

Lines changed: 47 additions & 20 deletions

File tree

docs/data/sql_functions.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ comparison:
5050
description: By default (or with the ASYMMETRIC keyword), returns TRUE if value1 is less than value2 or greater than value3. With the SYMMETRIC keyword, returns TRUE if value1 is not inclusively between value2 and value3. When either value2 or value3 is NULL, returns TRUE or UNKNOWN. E.g., 12 NOT BETWEEN 15 AND 12 returns TRUE; 12 NOT BETWEEN SYMMETRIC 15 AND 12 returns FALSE; 12 NOT BETWEEN NULL AND 15 returns UNKNOWN; 12 NOT BETWEEN 15 AND NULL returns TRUE; 12 NOT BETWEEN SYMMETRIC 12 AND NULL returns UNKNOWN.
5151
- sql: string1 LIKE string2 [ ESCAPE char ]
5252
table: string1.like(string2[, char])
53-
description: Returns TRUE if string1 matches pattern string2; returns UNKNOWN if string1 or string2 is NULL. An escape character consisting of a single char can be defined if necessary, '\' by default.
53+
description: Returns TRUE if string1 matches pattern string2; returns UNKNOWN if string1 or string2 is NULL. An escape character consisting of a single char can be defined using the ESCAPE clause if necessary. There is no default escape character.
5454
- sql: string1 NOT LIKE string2 [ ESCAPE char ]
55-
description: Returns TRUE if string1 does not match pattern string2; returns UNKNOWN if string1 or string2 is NULL. An escape character consisting of a single char can be defined if necessary, '\' by default.
55+
description: Returns TRUE if string1 does not match pattern string2; returns UNKNOWN if string1 or string2 is NULL. An escape character consisting of a single char can be defined using the ESCAPE clause if necessary. There is no default escape character.
5656
- sql: string1 SIMILAR TO string2 [ ESCAPE char ]
5757
table: string1.similar(string2)
5858
description: Returns TRUE if string1 matches SQL regular expression string2; returns UNKNOWN if string1 or string2 is NULL. An escape character can be defined if necessary. The escape character has not been supported yet.

docs/data/sql_functions_zh.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ comparison:
7272
table: string1.like(string2[, char])
7373
description: |
7474
如果 string1 匹配 string2 返回 `TRUE`;如果 string1 或 string2 为 `NULL` 返回 `UNKNOWN`。
75-
如果需要可以定义包含单个字符的转义字符,默认为 '\'
75+
如果需要,可以通过 ESCAPE 子句定义包含单个字符的转义字符。默认没有转义字符
7676
- sql: string1 NOT LIKE string2 [ ESCAPE char ]
7777
description: |
7878
如果 string1 与 string2 不匹配返回 `TRUE`;如果 string1 或 string2 为 `NULL` 返回 `UNKNOWN`。
79-
如果需要可以定义包含单个字符的转义字符,默认为 '\'
79+
如果需要,可以通过 ESCAPE 子句定义包含单个字符的转义字符。默认没有转义字符
8080
- sql: string1 SIMILAR TO string2 [ ESCAPE char ]
8181
table: string1.similar(string2)
8282
description: |

flink-python/pyflink/table/expression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,10 +1219,10 @@ def like(self,
12191219
pattern: Union[str, 'Expression[str]'] = None,
12201220
escape=None) -> 'Expression[bool]':
12211221
"""
1222-
Returns true, if a string matches the specified LIKE pattern
1222+
Returns true, if a string matches the specified LIKE pattern.
12231223
e.g. 'Jo_n%' matches all strings that start with 'Jo(arbitrary letter)n'.
1224-
An escape character consisting of a single char can be defined if necessary,
1225-
'\\' by default.
1224+
An escape character consisting of a single char can be defined if necessary.
1225+
There is no default escape character.
12261226
"""
12271227
if escape is None:
12281228
return _binary_op("like")(self, pattern)

flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/BaseExpressions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,8 +1087,8 @@ public OutType initCap() {
10871087
}
10881088

10891089
/**
1090-
* Returns true, if a string matches the specified LIKE pattern with default escape character
1091-
* '/'.
1090+
* Returns true, if a string matches the specified LIKE pattern. There is no default escape
1091+
* character.
10921092
*
10931093
* <p>e.g. "Jo_n%" matches all strings that start with "Jo(arbitrary letter)n"
10941094
*/

flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/functions/SqlLikeUtils.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,11 @@ public static String sqlToRegexLike(String sqlPattern, CharSequence escapeStr) {
9696
throw invalidEscapeCharacter(escapeStr.toString());
9797
}
9898
escapeChar = escapeStr.charAt(0);
99+
if (escapeChar == 0) {
100+
throw invalidEscapeCharacter(escapeStr.toString());
101+
}
99102
} else {
100-
escapeChar = '\\';
103+
escapeChar = 0;
101104
}
102105
return sqlToRegexLike(sqlPattern, escapeChar);
103106
}
@@ -109,7 +112,7 @@ static String sqlToRegexLike(String sqlPattern, char escapeChar) {
109112
final StringBuilder javaPattern = new StringBuilder(len + len);
110113
for (i = 0; i < len; i++) {
111114
char c = sqlPattern.charAt(i);
112-
if (c == escapeChar) {
115+
if (c == escapeChar && escapeChar != 0) {
113116
if (i == (sqlPattern.length() - 1)) {
114117
throw invalidEscapeSequence(sqlPattern, i);
115118
}
@@ -192,7 +195,7 @@ private static int sqlSimilarRewriteCharEnumeration(
192195
char c = sqlPattern.charAt(i);
193196
if (c == ']') {
194197
return i - 1;
195-
} else if (c == escapeChar) {
198+
} else if (c == escapeChar && escapeChar != 0) {
196199
i++;
197200
char nextChar = sqlPattern.charAt(i);
198201
if (SQL_SIMILAR_SPECIALS.indexOf(nextChar) >= 0) {
@@ -241,6 +244,9 @@ public static String sqlToRegexSimilar(String sqlPattern, CharSequence escapeStr
241244
throw invalidEscapeCharacter(escapeStr.toString());
242245
}
243246
escapeChar = escapeStr.charAt(0);
247+
if (escapeChar == 0) {
248+
throw invalidEscapeCharacter(escapeStr.toString());
249+
}
244250
} else {
245251
escapeChar = 0;
246252
}
@@ -256,7 +262,7 @@ public static String sqlToRegexSimilar(String sqlPattern, char escapeChar) {
256262
final int len = sqlPattern.length();
257263
for (int i = 0; i < len; i++) {
258264
char c = sqlPattern.charAt(i);
259-
if (c == escapeChar) {
265+
if (c == escapeChar && escapeChar != 0) {
260266
if (i == (len - 1)) {
261267
// It should never reach here after the escape rule
262268
// checking.

flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/LikeCallGen.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class LikeCallGen extends CallGenerator {
6565
throw SqlLikeUtils.invalidEscapeCharacter(escape)
6666
}
6767
val escapeChar = escape.charAt(escape.length - 1)
68+
if (escapeChar == 0) {
69+
throw SqlLikeUtils.invalidEscapeCharacter(escape)
70+
}
6871
var matched = true
6972
var i = 0
7073
val newBuilder = new StringBuilder

flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkSqlLikeUtilsTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ void testSqlLike() {
3636
assertThat(SqlLikeUtils.like("abcd", "a.*d", "\\")).isEqualTo(false);
3737
assertThat(SqlLikeUtils.like("abcde", "%c.e", "\\")).isEqualTo(false);
3838

39-
// default escape character
39+
// no default escape character - backslash is treated as a literal character
4040
assertThat(SqlLikeUtils.like("a-c", "a\\_c")).isEqualTo(false);
41-
assertThat(SqlLikeUtils.like("a_c", "a\\_c")).isEqualTo(true);
41+
assertThat(SqlLikeUtils.like("a_c", "a\\_c")).isEqualTo(false);
42+
assertThat(SqlLikeUtils.like("a\\_c", "a\\_c")).isEqualTo(true);
43+
44+
// default escape also excludes \u0000
45+
assertThat(SqlLikeUtils.like("_", "\u0000_", null)).isEqualTo(false);
46+
assertThat(SqlLikeUtils.like("\u0000x", "\u0000_", null)).isEqualTo(true);
4247

4348
// -------------------------------- sqlToRegexLike ----------------------------------------
4449

@@ -66,5 +71,8 @@ void testSqlLike() {
6671
assertThat(SqlLikeUtils.similar("abc", "a.c", "\\")).isEqualTo(true);
6772
assertThat(SqlLikeUtils.similar("a.c", "a.c", "\\")).isEqualTo(true);
6873
assertThat(SqlLikeUtils.similar("abcd", "a.*d", "\\")).isEqualTo(true);
74+
// default escape also excludes \u0000
75+
assertThat(SqlLikeUtils.similar("_", "\u0000_", null)).isEqualTo(false);
76+
assertThat(SqlLikeUtils.similar("\u0000x", "\u0000_", null)).isEqualTo(true);
6977
}
7078
}

flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/LikeFunctionITCase.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ private Stream<TestSetSpec> withEscape() {
204204
.testSqlResult(
205205
"'\btest\ne\\nd\f' LIKE '\btest\ne\\nd\f' ESCAPE '!'",
206206
true,
207-
DataTypes.BOOLEAN().notNull()));
207+
DataTypes.BOOLEAN().notNull())
208+
// Invalid escape character
209+
.testSqlValidationError(
210+
"f0 LIKE 'test' ESCAPE '\u0000'",
211+
"Invalid escape character '\u0000'"));
208212
}
209213
}

flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,22 +408,24 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
408408
testAllApis("abcxxxdef".like("%abc%qef%"), "'abcxxxdef' LIKE '%abc%qef%'", "FALSE")
409409
testAllApis("abcxxxdef".like("abc%qef"), "'abcxxxdef' LIKE 'abc%qef'", "FALSE")
410410

411-
// reported in FLINK-36100
411+
// reported in FLINK-36100 - without ESCAPE clause, '\' is a literal character
412412
testAllApis("TE_ST".like("%E_S%"), "'TE_ST' LIKE '%E_S%'", "TRUE")
413413
testAllApis("TE-ST".like("%E_S%"), "'TE-ST' LIKE '%E_S%'", "TRUE")
414-
testAllApis("TE_ST".like("%E\\_S%"), "'TE_ST' LIKE '%E\\_S%'", "TRUE")
414+
testAllApis("TE_ST".like("%E\\_S%"), "'TE_ST' LIKE '%E\\_S%'", "FALSE")
415415
testAllApis("TE-ST".like("%E\\_S%"), "'TE-ST' LIKE '%E\\_S%'", "FALSE")
416+
testAllApis("\u0000".like("\u0000"), "'\u0000' LIKE '\u0000'", "TRUE")
417+
testAllApis("a".like("\u0000_"), "'a' LIKE '\u0000_'", "FALSE")
416418
}
417419

418420
@Test
419421
def testNotLike(): Unit = {
420422
testAllApis(!'f0.like("Th_s%"), "f0 NOT LIKE 'Th_s%'", "FALSE")
421423
testAllApis(!'f0.like("%is a%"), "f0 NOT LIKE '%is a%'", "FALSE")
422424

423-
// reported in FLINK-36100
425+
// reported in FLINK-36100 - without ESCAPE clause, '\' is a literal character
424426
testSqlApi("'TE_ST' NOT LIKE '%E_S%'", "FALSE")
425427
testSqlApi("'TE-ST' NOT LIKE '%E_S%'", "FALSE")
426-
testSqlApi("'TE_ST' NOT LIKE '%E\\_S%'", "FALSE")
428+
testSqlApi("'TE_ST' NOT LIKE '%E\\_S%'", "TRUE")
427429
testSqlApi("'TE-ST' NOT LIKE '%E\\_S%'", "TRUE")
428430
}
429431

@@ -443,6 +445,8 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
443445
testAllApis("TE_ST".like("%E__S%", "_"), "'TE_ST' LIKE '%E__S%' ESCAPE '_'", "TRUE")
444446
testAllApis("TE-ST".like("TE%_ST", "%"), "'TE-ST' LIKE 'TE%_ST' ESCAPE '%'", "FALSE")
445447
testAllApis("TE_ST".like("TE%_ST", "%"), "'TE_ST' LIKE 'TE%_ST' ESCAPE '%'", "TRUE")
448+
testAllApis("TE-ST".like("%E*_S%", "*"), "'TE-ST' LIKE '%E*_S%' ESCAPE '*'", "FALSE")
449+
testAllApis("TE_ST".like("%E*_S%", "*"), "'TE_ST' LIKE '%E*_S%' ESCAPE '*'", "TRUE")
446450

447451
// special character in Java Regex
448452
testAllApis("TE-ST".like("%E\\_S%", "\\"), "'TE-ST' LIKE '%E\\_S%' ESCAPE '\\'", "FALSE")
@@ -498,6 +502,8 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
498502
testSqlApi("'TE_ST' NOT LIKE '%E__S%' ESCAPE '_'", "FALSE")
499503
testSqlApi("'TE-ST' NOT LIKE 'TE%_ST' ESCAPE '%'", "TRUE")
500504
testSqlApi("'TE_ST' NOT LIKE 'TE%_ST' ESCAPE '%'", "FALSE")
505+
testSqlApi("'TE-ST' NOT LIKE '%E*_S%' ESCAPE '*'", "TRUE")
506+
testSqlApi("'TE_ST' NOT LIKE '%E*_S%' ESCAPE '*'", "FALSE")
501507

502508
// special character in Java Regex
503509
testSqlApi("'TE-ST' NOT LIKE '%E\\_S%' ESCAPE '\\'", "TRUE")

0 commit comments

Comments
 (0)