From bd481d7808bf61799a03235aa9d55b1170ed5bf9 Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Tue, 17 Jun 2025 10:04:04 -0400 Subject: [PATCH 1/9] LikeListPushdown --- .../index/query/AutomatonQueryBuilder.java | 85 +++++++++++++++++++ .../core/querydsl/query/AutomatonQuery.java | 62 ++++++++++++++ .../esql/qa/single_node/PushQueriesIT.java | 26 +++--- .../scalar/string/regex/WildcardLikeList.java | 10 +-- 4 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java create mode 100644 x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java diff --git a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java new file mode 100644 index 0000000000000..657a90d59e0a0 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.index.query; + +import org.apache.lucene.index.Term; +import org.apache.lucene.search.AutomatonQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Objects; + +/** + * Implements an Automaton query, which matches documents based on a Lucene Automaton. + * It does not support serialization or XContent representation, + */ +public class AutomatonQueryBuilder extends AbstractQueryBuilder implements MultiTermQueryBuilder { + private final String fieldName; + private final Automaton automaton; + + public AutomatonQueryBuilder(String fieldName, Automaton automaton) { + if (Strings.isEmpty(fieldName)) { + throw new IllegalArgumentException("field name is null or empty"); + } + if (automaton == null) { + throw new IllegalArgumentException("automaton cannot be null"); + } + this.fieldName = fieldName; + this.automaton = automaton; + } + + @Override + public String fieldName() { + return fieldName; + } + + @Override + public String getWriteableName() { + throw new UnsupportedOperationException("AutomatonQueryBuilder does not support getWriteableName"); + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + throw new UnsupportedEncodingException("AutomatonQueryBuilder does not support doWriteTo"); + } + + @Override + protected void doXContent(XContentBuilder builder, Params params) throws IOException { + throw new UnsupportedEncodingException("AutomatonQueryBuilder does not support doXContent"); + } + + @Override + protected Query doToQuery(SearchExecutionContext context) throws IOException { + return new AutomatonQuery(new Term(fieldName), automaton); + } + + @Override + protected int doHashCode() { + return Objects.hash(fieldName, automaton); + } + + @Override + protected boolean doEquals(AutomatonQueryBuilder other) { + return Objects.equals(fieldName, other.fieldName) && Objects.equals(automaton, other.automaton); + } + + // TO DO, what should be the minimal supported version? + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ZERO; + } +} diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java new file mode 100644 index 0000000000000..b375fc248dde3 --- /dev/null +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.esql.core.querydsl.query; + +import org.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.index.query.AutomatonQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.xpack.esql.core.tree.Source; + +import java.util.Objects; + +/** + * Query that matches documents based on a Lucene Automaton. + */ +public class AutomatonQuery extends Query { + + private final String field; + private final Automaton automaton; + + public AutomatonQuery(Source source, String field, Automaton automaton) { + super(source); + this.field = field; + this.automaton = automaton; + } + + public String field() { + return field; + } + + @Override + protected QueryBuilder asBuilder() { + return new AutomatonQueryBuilder(field, automaton); + } + + @Override + public int hashCode() { + return Objects.hash(field, automaton); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + AutomatonQuery other = (AutomatonQuery) obj; + return Objects.equals(field, other.field) && Objects.equals(automaton, other.automaton); + } + + @Override + protected String innerToString() { + return "AutomatonQuery{" + "field='" + field + '\'' + '}'; + } +} diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java index a4db00fce1bde..ec05a3542424a 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java @@ -264,13 +264,13 @@ public void testLikeList() throws IOException { | WHERE test like ("%value*", "abc*") """; String luceneQuery = switch (type) { - case KEYWORD, CONSTANT_KEYWORD, MATCH_ONLY_TEXT_WITH_KEYWORD, AUTO, TEXT_WITH_KEYWORD -> "*:*"; + case CONSTANT_KEYWORD, MATCH_ONLY_TEXT_WITH_KEYWORD, AUTO, TEXT_WITH_KEYWORD -> "*:*"; case SEMANTIC_TEXT_WITH_KEYWORD -> "FieldExistsQuery [field=_primary_term]"; + case KEYWORD -> "test:AutomatonQuery"; }; ComputeSignature dataNodeSignature = switch (type) { - case CONSTANT_KEYWORD -> ComputeSignature.FILTER_IN_QUERY; - case AUTO, KEYWORD, TEXT_WITH_KEYWORD, MATCH_ONLY_TEXT_WITH_KEYWORD, SEMANTIC_TEXT_WITH_KEYWORD -> - ComputeSignature.FILTER_IN_COMPUTE; + case CONSTANT_KEYWORD, KEYWORD -> ComputeSignature.FILTER_IN_QUERY; + case AUTO, TEXT_WITH_KEYWORD, MATCH_ONLY_TEXT_WITH_KEYWORD, SEMANTIC_TEXT_WITH_KEYWORD -> ComputeSignature.FILTER_IN_COMPUTE; }; testPushQuery(value, esqlQuery, List.of(luceneQuery), dataNodeSignature, true); } @@ -324,12 +324,18 @@ private void testPushQuery( matchesList().item(matchesMap().entry("name", "test").entry("type", anyOf(equalTo("text"), equalTo("keyword")))), equalTo(found ? List.of(List.of(value)) : List.of()) ); - Matcher luceneQueryMatcher = anyOf( - () -> Iterators.map( - luceneQueryOptions.iterator(), - (String s) -> equalTo(s.replaceAll("%value", value).replaceAll("%different_value", differentValue)) - ) - ); + + Matcher luceneQueryMatcher; + if (luceneQueryOptions.size() == 1 && luceneQueryOptions.get(0).equals("test:AutomatonQuery")) { + luceneQueryMatcher = anyOf(startsWith("test:AutomatonQuery")); + } else { + luceneQueryMatcher = anyOf( + () -> Iterators.map( + luceneQueryOptions.iterator(), + (String s) -> equalTo(s.replaceAll("%value", value).replaceAll("%different_value", differentValue)) + ) + ); + } @SuppressWarnings("unchecked") List> profiles = (List>) ((Map) result.get("profile")).get("drivers"); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java index 4be51eddc0223..705be64d00207 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java @@ -13,6 +13,7 @@ import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPatternList; +import org.elasticsearch.xpack.esql.core.querydsl.query.AutomatonQuery; import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.querydsl.query.WildcardQuery; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; @@ -89,10 +90,6 @@ protected WildcardLikeList replaceChild(Expression newLeft) { */ @Override public Translatable translatable(LucenePushdownPredicates pushdownPredicates) { - if (pattern().patternList().size() != 1) { - // we only support a single pattern in the list for pushdown for now - return Translatable.NO; - } return pushdownPredicates.isPushableAttribute(field()) ? Translatable.YES : Translatable.NO; } @@ -113,9 +110,6 @@ public Query asQuery(LucenePushdownPredicates pushdownPredicates, TranslatorHand * Throws an {@link IllegalArgumentException} if the pattern list contains more than one pattern. */ private Query translateField(String targetFieldName) { - if (pattern().patternList().size() != 1) { - throw new IllegalArgumentException("WildcardLikeList can only be translated when it has a single pattern"); - } - return new WildcardQuery(source(), targetFieldName, pattern().patternList().getFirst().asLuceneWildcard(), caseInsensitive()); + return new AutomatonQuery(source(), targetFieldName, pattern().createAutomaton(caseInsensitive())); } } From 3b9b494b2b831c98f2e4b269c01d537cc688e0f7 Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Tue, 17 Jun 2025 12:45:38 -0400 Subject: [PATCH 2/9] Update docs/changelog/129557.yaml --- docs/changelog/129557.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/129557.yaml diff --git a/docs/changelog/129557.yaml b/docs/changelog/129557.yaml new file mode 100644 index 0000000000000..99afe8e45b439 --- /dev/null +++ b/docs/changelog/129557.yaml @@ -0,0 +1,5 @@ +pr: 129557 +summary: Pushdown for LIKE (LIST) +area: ES|QL +type: enhancement +issues: [] From 38b8e546e873bbf4c390656e028af59db19218a4 Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Tue, 17 Jun 2025 16:39:36 -0400 Subject: [PATCH 3/9] Address code review comments --- .../org/elasticsearch/index/query/AutomatonQueryBuilder.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java index 657a90d59e0a0..c67db63d665d7 100644 --- a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java @@ -14,7 +14,6 @@ import org.apache.lucene.search.Query; import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.TransportVersion; -import org.elasticsearch.TransportVersions; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xcontent.XContentBuilder; @@ -77,9 +76,8 @@ protected boolean doEquals(AutomatonQueryBuilder other) { return Objects.equals(fieldName, other.fieldName) && Objects.equals(automaton, other.automaton); } - // TO DO, what should be the minimal supported version? @Override public TransportVersion getMinimalSupportedVersion() { - return TransportVersions.ZERO; + throw new UnsupportedOperationException("AutomatonQueryBuilder does not support getMinimalSupportedVersion"); } } From fcd6ce727656825d41dde7b0d96ed80e3180d08d Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Fri, 20 Jun 2025 14:31:54 -0400 Subject: [PATCH 4/9] Add printable discription for push down Automata --- .../index/query/AutomatonQueryBuilder.java | 23 +++++++++++++++++-- .../core/querydsl/query/AutomatonQuery.java | 6 +++-- .../esql/qa/single_node/PushQueriesIT.java | 19 ++++++--------- .../scalar/string/regex/WildcardLikeList.java | 15 +++++++++++- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java index c67db63d665d7..b1b3774aa004e 100644 --- a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java @@ -29,8 +29,10 @@ public class AutomatonQueryBuilder extends AbstractQueryBuilder implements MultiTermQueryBuilder { private final String fieldName; private final Automaton automaton; + private final String description; - public AutomatonQueryBuilder(String fieldName, Automaton automaton) { + public AutomatonQueryBuilder(String fieldName, Automaton automaton, String description) { + this.description = description; if (Strings.isEmpty(fieldName)) { throw new IllegalArgumentException("field name is null or empty"); } @@ -63,7 +65,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep @Override protected Query doToQuery(SearchExecutionContext context) throws IOException { - return new AutomatonQuery(new Term(fieldName), automaton); + return new AutomatonQueryWithDescription(new Term(fieldName), automaton, description); } @Override @@ -80,4 +82,21 @@ protected boolean doEquals(AutomatonQueryBuilder other) { public TransportVersion getMinimalSupportedVersion() { throw new UnsupportedOperationException("AutomatonQueryBuilder does not support getMinimalSupportedVersion"); } + + static class AutomatonQueryWithDescription extends AutomatonQuery { + private final String description; + + AutomatonQueryWithDescription(Term term, Automaton automaton, String description) { + super(term, automaton); + this.description = description; + } + + @Override + public String toString(String field) { + if(description.isEmpty()) { + return super.toString(field); + } + return description; + } + } } diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java index b375fc248dde3..07b5d3e729dca 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java @@ -20,11 +20,13 @@ public class AutomatonQuery extends Query { private final String field; private final Automaton automaton; + private final String automatonDescription; - public AutomatonQuery(Source source, String field, Automaton automaton) { + public AutomatonQuery(Source source, String field, Automaton automaton, String automatonDescription) { super(source); this.field = field; this.automaton = automaton; + this.automatonDescription = automatonDescription; } public String field() { @@ -33,7 +35,7 @@ public String field() { @Override protected QueryBuilder asBuilder() { - return new AutomatonQueryBuilder(field, automaton); + return new AutomatonQueryBuilder(field, automaton, automatonDescription); } @Override diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java index 660b6082f6a7a..33a2edb2ef14d 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java @@ -266,7 +266,7 @@ public void testLikeList() throws IOException { String luceneQuery = switch (type) { case CONSTANT_KEYWORD, MATCH_ONLY_TEXT_WITH_KEYWORD, AUTO, TEXT_WITH_KEYWORD -> "*:*"; case SEMANTIC_TEXT_WITH_KEYWORD -> "FieldExistsQuery [field=_primary_term]"; - case KEYWORD -> "test:AutomatonQuery"; + case KEYWORD -> "Automaton for test LIKE (\"%value*\", \"abc*\"), caseInsensitive=false"; }; ComputeSignature dataNodeSignature = switch (type) { case CONSTANT_KEYWORD, KEYWORD -> ComputeSignature.FILTER_IN_QUERY; @@ -325,17 +325,12 @@ private void testPushQuery( equalTo(found ? List.of(List.of(value)) : List.of()) ); - Matcher luceneQueryMatcher; - if (luceneQueryOptions.size() == 1 && luceneQueryOptions.get(0).equals("test:AutomatonQuery")) { - luceneQueryMatcher = anyOf(startsWith("test:AutomatonQuery")); - } else { - luceneQueryMatcher = anyOf( - () -> Iterators.map( - luceneQueryOptions.iterator(), - (String s) -> equalTo(s.replaceAll("%value", value).replaceAll("%different_value", differentValue)) - ) - ); - } + Matcher luceneQueryMatcher = anyOf( + () -> Iterators.map( + luceneQueryOptions.iterator(), + (String s) -> equalTo(s.replaceAll("%value", value).replaceAll("%different_value", differentValue)) + ) + ); @SuppressWarnings("unchecked") List> profiles = (List>) ((Map) result.get("profile")).get("drivers"); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java index 705be64d00207..8252d40c3d044 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern; import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPatternList; import org.elasticsearch.xpack.esql.core.querydsl.query.AutomatonQuery; import org.elasticsearch.xpack.esql.core.querydsl.query.Query; @@ -24,6 +25,7 @@ import org.elasticsearch.xpack.esql.planner.TranslatorHandler; import java.io.IOException; +import java.util.stream.Collectors; public class WildcardLikeList extends RegexMatch { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( @@ -110,6 +112,17 @@ public Query asQuery(LucenePushdownPredicates pushdownPredicates, TranslatorHand * Throws an {@link IllegalArgumentException} if the pattern list contains more than one pattern. */ private Query translateField(String targetFieldName) { - return new AutomatonQuery(source(), targetFieldName, pattern().createAutomaton(caseInsensitive())); + return new AutomatonQuery( + source(), + targetFieldName, + pattern().createAutomaton(caseInsensitive()), + getAutomatonDescription(targetFieldName) + ); + } + + private String getAutomatonDescription(String targetFieldName) { + // we use all the information used the create the automaton to describe the query here + String patternDesc = pattern().patternList().stream().map(WildcardPattern::pattern).collect(Collectors.joining("\", \"")); + return "Automaton for " + targetFieldName + " LIKE (\"" + patternDesc + "\"), caseInsensitive=" + caseInsensitive(); } } From 367cdd9e5097b1f341b20174e3132296dcb4284b Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Fri, 20 Jun 2025 14:36:33 -0400 Subject: [PATCH 5/9] Add printable description for push down Automata --- .../elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java index 33a2edb2ef14d..7d1e28e8c5264 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java @@ -324,7 +324,6 @@ private void testPushQuery( matchesList().item(matchesMap().entry("name", "test").entry("type", anyOf(equalTo("text"), equalTo("keyword")))), equalTo(found ? List.of(List.of(value)) : List.of()) ); - Matcher luceneQueryMatcher = anyOf( () -> Iterators.map( luceneQueryOptions.iterator(), From 58b997820e898fbd779cf7fb28ca2bc1ad2d4108 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 20 Jun 2025 18:47:25 +0000 Subject: [PATCH 6/9] [CI] Auto commit changes from spotless --- .../org/elasticsearch/index/query/AutomatonQueryBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java index b1b3774aa004e..0c828fc937692 100644 --- a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java @@ -93,7 +93,7 @@ static class AutomatonQueryWithDescription extends AutomatonQuery { @Override public String toString(String field) { - if(description.isEmpty()) { + if (description.isEmpty()) { return super.toString(field); } return description; From 567c5b904d42f6497a088c9e514113c7dc6c7b67 Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Mon, 23 Jun 2025 11:10:29 -0400 Subject: [PATCH 7/9] Address code review feedback --- .../index/query/AutomatonQueryBuilder.java | 16 +++++++++------- .../xpack/esql/qa/single_node/PushQueriesIT.java | 2 +- .../scalar/string/regex/WildcardLikeList.java | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java index 0c828fc937692..9c6331044e6d5 100644 --- a/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/AutomatonQueryBuilder.java @@ -24,7 +24,7 @@ /** * Implements an Automaton query, which matches documents based on a Lucene Automaton. - * It does not support serialization or XContent representation, + * It does not support serialization or XContent representation. */ public class AutomatonQueryBuilder extends AbstractQueryBuilder implements MultiTermQueryBuilder { private final String fieldName; @@ -32,7 +32,6 @@ public class AutomatonQueryBuilder extends AbstractQueryBuilder "*:*"; case SEMANTIC_TEXT_WITH_KEYWORD -> "FieldExistsQuery [field=_primary_term]"; - case KEYWORD -> "Automaton for test LIKE (\"%value*\", \"abc*\"), caseInsensitive=false"; + case KEYWORD -> "test:LIKE(\"%value*\", \"abc*\"), caseInsensitive=false"; }; ComputeSignature dataNodeSignature = switch (type) { case CONSTANT_KEYWORD, KEYWORD -> ComputeSignature.FILTER_IN_QUERY; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java index 8252d40c3d044..e52eacf299a35 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java @@ -123,6 +123,6 @@ private Query translateField(String targetFieldName) { private String getAutomatonDescription(String targetFieldName) { // we use all the information used the create the automaton to describe the query here String patternDesc = pattern().patternList().stream().map(WildcardPattern::pattern).collect(Collectors.joining("\", \"")); - return "Automaton for " + targetFieldName + " LIKE (\"" + patternDesc + "\"), caseInsensitive=" + caseInsensitive(); + return "LIKE(\"" + patternDesc + "\"), caseInsensitive=" + caseInsensitive(); } } From 9e8c63bb28a6574d52b62db4841b46362453cd36 Mon Sep 17 00:00:00 2001 From: Julian Kiryakov Date: Mon, 23 Jun 2025 11:37:03 -0400 Subject: [PATCH 8/9] Add support for RLIKE (LIST) --- .../esql/core/querydsl/query/AutomatonQuery.java | 4 ++-- .../scalar/string/regex/WildcardLikeList.java | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java index 07b5d3e729dca..3713ccd9d9f01 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java @@ -40,7 +40,7 @@ protected QueryBuilder asBuilder() { @Override public int hashCode() { - return Objects.hash(field, automaton); + return Objects.hash(field, automaton, automatonDescription); } @Override @@ -54,7 +54,7 @@ public boolean equals(Object obj) { } AutomatonQuery other = (AutomatonQuery) obj; - return Objects.equals(field, other.field) && Objects.equals(automaton, other.automaton); + return Objects.equals(field, other.field) && Objects.equals(automaton, other.automaton) && Objects.equals(automatonDescription, other.automatonDescription); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java index e52eacf299a35..0b58594779408 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/regex/WildcardLikeList.java @@ -112,16 +112,11 @@ public Query asQuery(LucenePushdownPredicates pushdownPredicates, TranslatorHand * Throws an {@link IllegalArgumentException} if the pattern list contains more than one pattern. */ private Query translateField(String targetFieldName) { - return new AutomatonQuery( - source(), - targetFieldName, - pattern().createAutomaton(caseInsensitive()), - getAutomatonDescription(targetFieldName) - ); + return new AutomatonQuery(source(), targetFieldName, pattern().createAutomaton(caseInsensitive()), getAutomatonDescription()); } - private String getAutomatonDescription(String targetFieldName) { - // we use all the information used the create the automaton to describe the query here + private String getAutomatonDescription() { + // we use the information used to create the automaton to describe the query here String patternDesc = pattern().patternList().stream().map(WildcardPattern::pattern).collect(Collectors.joining("\", \"")); return "LIKE(\"" + patternDesc + "\"), caseInsensitive=" + caseInsensitive(); } From 83835adc9104b3a019c611ae7cd12a8bacc62007 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 23 Jun 2025 15:48:48 +0000 Subject: [PATCH 9/9] [CI] Auto commit changes from spotless --- .../xpack/esql/core/querydsl/query/AutomatonQuery.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java index 3713ccd9d9f01..343cabc6feea9 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/AutomatonQuery.java @@ -54,7 +54,9 @@ public boolean equals(Object obj) { } AutomatonQuery other = (AutomatonQuery) obj; - return Objects.equals(field, other.field) && Objects.equals(automaton, other.automaton) && Objects.equals(automatonDescription, other.automatonDescription); + return Objects.equals(field, other.field) + && Objects.equals(automaton, other.automaton) + && Objects.equals(automatonDescription, other.automatonDescription); } @Override