diff --git a/docs/changelog/128750.yaml b/docs/changelog/128750.yaml new file mode 100644 index 0000000000000..55971accbcb37 --- /dev/null +++ b/docs/changelog/128750.yaml @@ -0,0 +1,7 @@ +pr: 128750 +summary: Fix conversion of a Lucene wildcard pattern to a regexp +area: ES|QL +type: bug +issues: + - 128677 + - 128676 diff --git a/muted-tests.yml b/muted-tests.yml index 52b27299ac6a0..7cb1b19316e89 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -489,12 +489,6 @@ tests: - class: org.elasticsearch.packaging.test.DockerTests method: test085EnvironmentVariablesAreRespectedUnderDockerExec issue: https://github.com/elastic/elasticsearch/issues/128115 -- class: org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLikeTests - method: testEvaluateInManyThreads {TestCase=100 random code points matches self case insensitive with keyword} - issue: https://github.com/elastic/elasticsearch/issues/128676 -- class: org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLikeTests - method: testEvaluateInManyThreads {TestCase=100 random code points matches self case insensitive with text} - issue: https://github.com/elastic/elasticsearch/issues/128677 - class: org.elasticsearch.xpack.esql.expression.function.scalar.string.RLikeTests method: testEvaluateInManyThreads {TestCase=100 random code points matches self case insensitive with text} issue: https://github.com/elastic/elasticsearch/issues/128705 diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/StringUtils.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/StringUtils.java index 2a5349309d9ee..b6b4782da3a54 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/StringUtils.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/StringUtils.java @@ -181,6 +181,7 @@ public static String wildcardToJavaPattern(String pattern, char escape) { /** * Translates a Lucene wildcard pattern to a Lucene RegExp one. + * Note: all RegExp "optional" characters are escaped too (allowing the use of the {@code RegExp.ALL} flag). * @param wildcard Lucene wildcard pattern * @return Lucene RegExp pattern */ @@ -209,7 +210,10 @@ public static String luceneWildcardToRegExp(String wildcard) { regex.append("\\\\"); } } - case '$', '(', ')', '+', '.', '[', ']', '^', '{', '|', '}' -> regex.append("\\").append(c); + // reserved RegExp characters + case '"', '$', '(', ')', '+', '.', '[', ']', '^', '{', '|', '}' -> regex.append("\\").append(c); + // reserved optional RegExp characters + case '#', '&', '<', '>' -> regex.append("\\").append(c); default -> regex.append(c); } } diff --git a/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/StringUtilsTests.java b/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/StringUtilsTests.java index fbb956177b0ad..4ad95d8433f5b 100644 --- a/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/StringUtilsTests.java +++ b/x-pack/plugin/esql-core/src/test/java/org/elasticsearch/xpack/esql/core/util/StringUtilsTests.java @@ -70,7 +70,10 @@ public void testLuceneWildcardToRegExp() { assertThat(luceneWildcardToRegExp("foo\\*bar"), is("foo\\*bar")); assertThat(luceneWildcardToRegExp("foo\\?bar\\?"), is("foo\\?bar\\?")); assertThat(luceneWildcardToRegExp("foo\\?bar\\"), is("foo\\?bar\\\\")); - assertThat(luceneWildcardToRegExp("[](){}^$.|+"), is("\\[\\]\\(\\)\\{\\}\\^\\$\\.\\|\\+")); + // reserved characters + assertThat(luceneWildcardToRegExp("\"[](){}^$.|+"), is("\\\"\\[\\]\\(\\)\\{\\}\\^\\$\\.\\|\\+")); + // reserved "optional" characters + assertThat(luceneWildcardToRegExp("#&<>"), is("\\#\\&\\<\\>")); assertThat(luceneWildcardToRegExp("foo\\\uD83D\uDC14bar"), is("foo\uD83D\uDC14bar")); assertThat(luceneWildcardToRegExp("foo\uD83D\uDC14bar"), is("foo\uD83D\uDC14bar")); } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java index c0cba1900dc37..02324f02577e6 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java @@ -1408,7 +1408,36 @@ public void testAsyncGetWithoutContentType() throws IOException { .item(matchesMap().entry("name", "integer").entry("type", "integer")), values ); + } + public void testReplaceStringCasingWithInsensitiveWildcardMatch() throws IOException { + createIndex(testIndexName(), Settings.EMPTY, """ + { + "properties": { + "reserved": { + "type": "keyword" + }, + "optional": { + "type": "keyword" + } + } + } + """); + Request doc = new Request("POST", testIndexName() + "/_doc?refresh=true"); + doc.setJsonEntity(""" + { + "reserved": "_\\"_$_(_)_+_._[_]_^_{_|_}___", + "optional": "_#_&_<_>___" + } + """); + client().performRequest(doc); + var query = "FROM " + testIndexName() + """ + | WHERE TO_LOWER(reserved) LIKE "_\\"_$_(_)_+_._[_]_^_{_|_}*" + | WHERE TO_LOWER(optional) LIKE "_#_&_<_>*" + | KEEP reserved, optional + """; + var answer = runEsql(requestObjectBuilder().query(query)); + assertThat(answer.get("values"), equalTo(List.of(List.of("_\"_$_(_)_+_._[_]_^_{_|_}___", "_#_&_<_>___")))); } protected static Request prepareRequestWithOptions(RequestObjectBuilder requestObject, Mode mode) throws IOException {