SQL: Escaped wildcard (*) not accepted in LIKE (#63428)

For a query like `SELECT name FROM test WHERE name LIKE ''%c*'` ES SQL
generates an error. `*` is not a special character in a `LIKE` construct
and it's expected to not needing to be escaped, so the previous query
should work as is.
In the LIKE pattern any `*` character was treated as invalid character
and the usage of `%` or `_` was suggested instead. But `*` is a valid,
acceptable non-wildcard on the right side of the `LIKE` operator.

Fix: #55108
(cherry picked from commit 190d9fe3deb31aed0d8f312007360625d4fff217)
This commit is contained in:
Andras Palinkas 2020-10-13 09:38:35 -04:00 committed by Andras Palinkas
parent f70391c6cc
commit 4aabc050a0
4 changed files with 47 additions and 22 deletions

View File

@ -33,8 +33,9 @@ with the `LIKE` operator:
* The percent sign (%)
* The underscore (_)
The percent sign represents zero, one or multiple characters. The underscore represents a single number or character. These symbols can be
used in combinations.
The percent sign represents zero, one or multiple characters. The underscore represents a single number or character. These symbols can be used in combinations.
NOTE: No other characters have special meaning or act as wildcard. Characters often used as wildcards in other languages (`*` or `?`) are treated as normal characters.
[source, sql]
----

View File

@ -270,12 +270,6 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
if (pattern == null) {
throw new ParsingException(source(ctx.value), "Pattern must not be [null]");
}
int pos = pattern.indexOf('*');
if (pos >= 0) {
throw new ParsingException(source(ctx.value),
"Invalid char [*] found in pattern [{}] at position {}; use [%] or [_] instead",
pattern, pos);
}
char escape = 0;
PatternEscapeContext escapeCtx = ctx.patternEscape();
@ -288,7 +282,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
} else if (escapeString.length() == 1) {
escape = escapeString.charAt(0);
// these chars already have a meaning
if (escape == '*' || escape == '%' || escape == '_') {
if (escape == '%' || escape == '_') {
throw new ParsingException(source(escapeCtx.escape), "Char [{}] cannot be used for escaping", escape);
}
// lastly validate that escape chars (if present) are followed by special chars
@ -303,8 +297,8 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
char next = pattern.charAt(i + 1);
if (next != '%' && next != '_') {
throw new ParsingException(source(ctx.value),
"Pattern [{}] is invalid as escape char [{}] at position {} can only escape wildcard chars; found [{}]",
pattern, escape, i, next);
"Pattern [{}] is invalid as escape char [{}] at position {} can only escape "
+ "wildcard chars [%_]; found [{}]", pattern, escape, i, next);
}
}
}

View File

@ -21,6 +21,12 @@ public class LikeEscapingParsingTests extends ESTestCase {
private final SqlParser parser = new SqlParser();
private static LikePattern patternOfLike(Expression exp) {
assertThat(exp, instanceOf(Like.class));
Like l = (Like) exp;
return l.pattern();
}
private String error(String pattern) {
ParsingException ex = expectThrows(ParsingException.class,
() -> parser.createExpression(format(null, "exp LIKE {}", pattern)));
@ -36,9 +42,11 @@ public class LikeEscapingParsingTests extends ESTestCase {
} else {
exp = parser.createExpression(format(null, "exp LIKE '{}'", pattern));
}
assertThat(exp, instanceOf(Like.class));
Like l = (Like) exp;
return l.pattern();
return patternOfLike(exp);
}
private LikePattern like(String pattern, Character escapeChar) {
return patternOfLike(parser.createExpression(format(null, "exp LIKE '{}' ESCAPE '{}'", pattern, escapeChar)));
}
public void testNoEscaping() {
@ -55,16 +63,34 @@ public class LikeEscapingParsingTests extends ESTestCase {
public void testEscapingWrongChar() {
assertThat(error("'|string' ESCAPE '|'"),
is("line 1:11: Pattern [|string] is invalid as escape char [|] at position 0 can only escape wildcard chars; found [s]"));
is("line 1:11: Pattern [|string] is invalid as escape char [|] at position 0 can only escape "
+ "wildcard chars [%_]; found [s]"));
}
public void testInvalidChar() {
assertThat(error("'%string' ESCAPE '%'"),
is("line 1:28: Char [%] cannot be used for escaping"));
public void testEscapingTheEscapeCharacter() {
assertThat(error("'||string' ESCAPE '|'"),
is("line 1:11: Pattern [||string] is invalid as escape char [|] at position 0 can only escape wildcard chars [%_]; found [|]"));
}
public void testCannotUseStar() {
assertThat(error("'|*string' ESCAPE '|'"),
is("line 1:11: Invalid char [*] found in pattern [|*string] at position 1; use [%] or [_] instead"));
public void testEscapingWildcards() {
assertThat(error("'string' ESCAPE '%'"),
is("line 1:27: Char [%] cannot be used for escaping"));
assertThat(error("'string' ESCAPE '_'"),
is("line 1:27: Char [_] cannot be used for escaping"));
}
public void testCanUseStarWithoutEscaping() {
LikePattern like = like("%string*");
assertThat(like.pattern(), is("%string*"));
assertThat(like.asJavaRegex(), is("^.*string\\*$"));
assertThat(like.asLuceneWildcard(), is("*string\\*"));
}
public void testEscapingWithStar() {
LikePattern like = like("*%%*__string", '*');
assertThat(like.pattern(), is("*%%*__string"));
assertThat(like.asJavaRegex(), is("^%.*_.string$"));
assertThat(like.asLuceneWildcard(), is("%*_?string"));
}
}

View File

@ -81,6 +81,10 @@ public class LikeConversionTests extends ESTestCase {
assertEquals("foo\\*bar*", wildcard("foo*bar%"));
}
public void testStarLiteralWithWildcards() {
assertEquals("\\**\\*?foo\\*\\*?*", wildcard("*%*_foo**_%"));
}
public void testWildcardEscapedWildcard() {
assertEquals("foo\\*bar%", wildcard("foo*bar|%"));
}
@ -129,4 +133,4 @@ public class LikeConversionTests extends ESTestCase {
assertEquals("foo|_bar|", unescape("foo|||_bar||"));
}
}
}