From ba933d914d67f945d02c6f0ba98376d7fb8b3f05 Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Tue, 21 Jan 2025 09:42:11 +0100 Subject: [PATCH 1/9] Add specific docs for match operator using a separate class for holding documentation --- .../functions/kibana/definition/match.json | 3 +- .../kibana/definition/match_operator.json | 2 +- .../functions/kibana/docs/match_operator.md | 12 +-- docs/reference/esql/functions/search.asciidoc | 14 +--- .../function/fulltext/FullTextWritables.java | 1 + .../expression/function/fulltext/Match.java | 5 +- .../function/fulltext/MatchOperator.java | 74 +++++++++++++++++++ .../function/AbstractFunctionTestCase.java | 3 +- .../function/fulltext/MatchOperatorTests.java | 8 ++ 9 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java diff --git a/docs/reference/esql/functions/kibana/definition/match.json b/docs/reference/esql/functions/kibana/definition/match.json index 1ad2c6dba9f81..eb206cb9ddf4d 100644 --- a/docs/reference/esql/functions/kibana/definition/match.json +++ b/docs/reference/esql/functions/kibana/definition/match.json @@ -1,7 +1,6 @@ { "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", - "type" : "operator", - "operator" : ":", + "type" : "eval", "name" : "match", "description" : "Use `MATCH` to perform a <> on the specified field.\nUsing `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL.\n\nMatch can be used on fields from the text family like <> and <>,\nas well as other field types like keyword, boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the <> `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.", "signatures" : [ diff --git a/docs/reference/esql/functions/kibana/definition/match_operator.json b/docs/reference/esql/functions/kibana/definition/match_operator.json index 665071aed55c4..a67c6b0e45c4a 100644 --- a/docs/reference/esql/functions/kibana/definition/match_operator.json +++ b/docs/reference/esql/functions/kibana/definition/match_operator.json @@ -3,7 +3,7 @@ "type" : "operator", "operator" : ":", "name" : "match_operator", - "description" : "Use `MATCH` to perform a <> on the specified field.\nUsing `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL.\n\nMatch can be used on fields from the text family like <> and <>,\nas well as other field types like keyword, boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the <> `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.", + "description" : "Use the match operator (`:`) to perform a <> on the specified field.\nUsing `:` is equivalent to using the `match` query in the Elasticsearch Query DSL.\n\nThe match operator is equivalent to the <>.\n\nFor using the function syntax, or adding <>, you can use the\n<>.\n\n`:` returns true if the provided query matches the row.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/docs/match_operator.md b/docs/reference/esql/functions/kibana/docs/match_operator.md index 98f55aacde0b8..0624329182f3a 100644 --- a/docs/reference/esql/functions/kibana/docs/match_operator.md +++ b/docs/reference/esql/functions/kibana/docs/match_operator.md @@ -3,15 +3,15 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ --> ### MATCH_OPERATOR -Use `MATCH` to perform a <> on the specified field. -Using `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL. +Use the match operator (`:`) to perform a <> on the specified field. +Using `:` is equivalent to using the `match` query in the Elasticsearch Query DSL. -Match can be used on fields from the text family like <> and <>, -as well as other field types like keyword, boolean, dates, and numeric types. +The match operator is equivalent to the <>. -For a simplified syntax, you can use the <> `:` operator instead of `MATCH`. +For using the function syntax, or adding <>, you can use the +<>. -`MATCH` returns true if the provided query matches the row. +`:` returns true if the provided query matches the row. ``` FROM books diff --git a/docs/reference/esql/functions/search.asciidoc b/docs/reference/esql/functions/search.asciidoc index ba399ead8adfc..45953310dd678 100644 --- a/docs/reference/esql/functions/search.asciidoc +++ b/docs/reference/esql/functions/search.asciidoc @@ -14,13 +14,7 @@ The match operator is equivalent to the <>. [.text-center] image::esql/functions/signature/match_operator.svg[Embedded,opts=inline] -include::types/match.asciidoc[] - -[source.merge.styled,esql] ----- -include::{esql-specs}/match-operator.csv-spec[tag=match-with-field] ----- -[%header.monospaced.styled,format=dsv,separator=|] -|=== -include::{esql-specs}/match-operator.csv-spec[tag=match-with-field-result] -|=== +include::../parameters/match_operator.asciidoc[] +include::../description/match_operator.asciidoc[] +include::../types/match_operator.asciidoc[] +include::../examples/match.asciidoc[] diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java index a3a14004b1c89..f42433c22e775 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java @@ -25,6 +25,7 @@ public static List getNamedWriteables() { entries.add(MultiMatchQueryPredicate.ENTRY); entries.add(QueryString.ENTRY); entries.add(Match.ENTRY); + entries.add(MatchOperator.ENTRY); entries.add(Kql.ENTRY); if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java index b7ebcda70b622..7729cc60599bf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java @@ -63,6 +63,8 @@ /** * Full text function that performs a {@link QueryStringQuery} . + * Unlike other ESQL functions, Match is both a function and an operator. This class provides implementation for both, + * but the operator documentation is generated in {@link MatchOperator}. */ public class Match extends FullTextFunction implements PostOptimizationVerificationAware { @@ -101,7 +103,6 @@ public class Match extends FullTextFunction implements PostOptimizationVerificat @FunctionInfo( returnType = "boolean", - operator = ":", preview = true, description = """ Use `MATCH` to perform a <> on the specified field. @@ -136,7 +137,7 @@ public Match(Source source, Expression field, Expression matchQuery, QueryBuilde this.field = field; } - private static Match readFrom(StreamInput in) throws IOException { + protected static Match readFrom(StreamInput in) throws IOException { Source source = Source.readFrom((PlanStreamInput) in); Expression field = in.readNamedWriteable(Expression.class); Expression query = in.readNamedWriteable(Expression.class); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java new file mode 100644 index 0000000000000..2d0cd8d18a5db --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -0,0 +1,74 @@ +/* + * 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.expression.function.fulltext; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.expression.function.Example; +import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; +import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; + +import java.io.IOException; + +/** + * This class exists to generate documentation for the match operator. + * Unlike other ESQL functions, Match is both a function and an operator. This class provides the documentation for the operator. + */ +public class MatchOperator extends Match { + + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MatchOperator", + MatchOperator::readFrom); + + @FunctionInfo( + returnType = "boolean", + operator = ":", + preview = true, + description = """ + Use the match operator (`:`) to perform a <> on the specified field. + Using `:` is equivalent to using the `match` query in the Elasticsearch Query DSL. + + The match operator is equivalent to the <>. + + For using the function syntax, or adding <>, you can use the + <>. + + `:` returns true if the provided query matches the row.""", + examples = { @Example(file = "match-function", tag = "match-with-field") } + ) + public MatchOperator( + Source source, + @Param( + name = "field", + type = { "keyword", "text", "boolean", "date", "date_nanos", "double", "integer", "ip", "long", "unsigned_long", "version" }, + description = "Field that the query will target." + ) Expression field, + @Param( + name = "query", + type = { "keyword", "boolean", "date", "date_nanos", "double", "integer", "ip", "long", "unsigned_long", "version" }, + description = "Value to find in the provided field." + ) Expression matchQuery + ) { + super(source, field, matchQuery); + } + + protected static Match readFrom(StreamInput in) throws IOException { + Source source = Source.readFrom((PlanStreamInput) in); + Expression field = in.readNamedWriteable(Expression.class); + Expression query = in.readNamedWriteable(Expression.class); + + return new MatchOperator(source, field, query); + } + + @Override + public String getWriteableName() { + return ENTRY.name; + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index 67dec69b51393..bf6d263a34302 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -45,6 +45,7 @@ import org.elasticsearch.xpack.esql.evaluator.EvalMapper; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; +import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike; @@ -139,7 +140,7 @@ public abstract class AbstractFunctionTestCase extends ESTestCase { entry("is_null", IsNull.class), entry("is_not_null", IsNotNull.class), // Match operator is both a function and an operator - entry("match_operator", Match.class) + entry("match_operator", MatchOperator.class) ); private static EsqlFunctionRegistry functionRegistry = new EsqlFunctionRegistry().snapshotRegistry(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java index 951aff80541bd..73205b18d472b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java @@ -10,9 +10,12 @@ import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.expression.function.FunctionName; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import java.util.List; import java.util.function.Supplier; /** @@ -29,4 +32,9 @@ public MatchOperatorTests(@Name("TestCase") Supplier public static Iterable parameters() { return MatchTests.parameters(); } + + @Override + protected Expression build(Source source, List args) { + return new MatchOperator(source, args.get(0), args.get(1)); + } } From a9ecc750d4f9125c6bafb022f0722f89ec7d013b Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Tue, 21 Jan 2025 10:01:45 +0100 Subject: [PATCH 2/9] Get description back to operators --- docs/reference/esql/functions/search.asciidoc | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/reference/esql/functions/search.asciidoc b/docs/reference/esql/functions/search.asciidoc index 45953310dd678..1be4dee58e234 100644 --- a/docs/reference/esql/functions/search.asciidoc +++ b/docs/reference/esql/functions/search.asciidoc @@ -11,10 +11,19 @@ Returns true if the provided query matches the row. The match operator is equivalent to the <>. +For using the function syntax, or adding <>, you can use the +<>. + [.text-center] image::esql/functions/signature/match_operator.svg[Embedded,opts=inline] -include::../parameters/match_operator.asciidoc[] -include::../description/match_operator.asciidoc[] -include::../types/match_operator.asciidoc[] -include::../examples/match.asciidoc[] +include::types/match_operator.asciidoc[] + +[source.merge.styled,esql] +---- +include::{esql-specs}/match-operator.csv-spec[tag=match-with-field] +---- +[%header.monospaced.styled,format=dsv,separator=|] +|=== +include::{esql-specs}/match-operator.csv-spec[tag=match-with-field-result] +|=== From bee34b067dafbddc12fd830aa90ce8534c3a7352 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 21 Jan 2025 09:12:24 +0000 Subject: [PATCH 3/9] [CI] Auto commit changes from spotless --- .../esql/expression/function/fulltext/MatchOperator.java | 7 +++++-- .../esql/expression/function/AbstractFunctionTestCase.java | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java index 2d0cd8d18a5db..1c4735ce4c177 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -24,8 +24,11 @@ */ public class MatchOperator extends Match { - public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MatchOperator", - MatchOperator::readFrom); + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( + Expression.class, + "MatchOperator", + MatchOperator::readFrom + ); @FunctionInfo( returnType = "boolean", diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index bf6d263a34302..d741aea43ac6a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -44,7 +44,6 @@ import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.evaluator.EvalMapper; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; -import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; From 76e3887b1f22f9c1013e278e831c33967cd6e4ef Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Tue, 21 Jan 2025 10:40:02 +0100 Subject: [PATCH 4/9] Add RLIKE and LIKE operator definitions --- docs/reference/esql/functions/kibana/definition/like.json | 3 ++- docs/reference/esql/functions/kibana/definition/rlike.json | 3 ++- .../xpack/esql/expression/function/scalar/string/RLike.java | 2 +- .../esql/expression/function/scalar/string/WildcardLike.java | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/reference/esql/functions/kibana/definition/like.json b/docs/reference/esql/functions/kibana/definition/like.json index 2fcb29622efbd..4a26dca276696 100644 --- a/docs/reference/esql/functions/kibana/definition/like.json +++ b/docs/reference/esql/functions/kibana/definition/like.json @@ -1,6 +1,7 @@ { "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", - "type" : "eval", + "type" : "operator", + "operator" : "LIKE", "name" : "like", "description" : "Use `LIKE` to filter data based on string patterns using wildcards. `LIKE`\nusually acts on a field placed on the left-hand side of the operator, but it can\nalso act on a constant (literal) expression. The right-hand side of the operator\nrepresents the pattern.\n\nThe following wildcard characters are supported:\n\n* `*` matches zero or more characters.\n* `?` matches one character.", "signatures" : [ diff --git a/docs/reference/esql/functions/kibana/definition/rlike.json b/docs/reference/esql/functions/kibana/definition/rlike.json index 47cbd7800c821..88d6c7d6bb9b2 100644 --- a/docs/reference/esql/functions/kibana/definition/rlike.json +++ b/docs/reference/esql/functions/kibana/definition/rlike.json @@ -1,6 +1,7 @@ { "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", - "type" : "eval", + "type" : "operator", + "operator" : "RLIKE", "name" : "rlike", "description" : "Use `RLIKE` to filter data based on string patterns using using\n<>. `RLIKE` usually acts on a field placed on\nthe left-hand side of the operator, but it can also act on a constant (literal)\nexpression. The right-hand side of the operator represents the pattern.", "signatures" : [ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java index 688cbbb992443..d9c3d68dda10a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java @@ -58,7 +58,7 @@ Matching special characters (eg. `.`, `*`, `(`...) will require escaping. ---- include::{esql-specs}/string.csv-spec[tag=rlikeEscapingTripleQuotes] ---- - """, examples = @Example(file = "docs", tag = "rlike")) + """, operator="RLIKE", examples = @Example(file = "docs", tag = "rlike")) public RLike( Source source, @Param(name = "str", type = { "keyword", "text" }, description = "A literal value.") Expression value, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java index 8c596ee032bee..d5f411dd4d410 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java @@ -69,7 +69,7 @@ also act on a constant (literal) expression. The right-hand side of the operator ---- include::{esql-specs}/string.csv-spec[tag=likeEscapingTripleQuotes] ---- - """, examples = @Example(file = "docs", tag = "like")) + """, operator="LIKE", examples = @Example(file = "docs", tag = "like")) public WildcardLike( Source source, @Param(name = "str", type = { "keyword", "text" }, description = "A literal expression.") Expression left, From a80d142a730fe931ca6d23eda09f1d0fbd92bdf4 Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Tue, 21 Jan 2025 10:41:27 +0100 Subject: [PATCH 5/9] Spotless --- .../esql/expression/function/fulltext/MatchOperator.java | 7 +++++-- .../esql/expression/function/scalar/string/RLike.java | 2 +- .../expression/function/scalar/string/WildcardLike.java | 2 +- .../esql/expression/function/AbstractFunctionTestCase.java | 1 - 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java index 2d0cd8d18a5db..1c4735ce4c177 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -24,8 +24,11 @@ */ public class MatchOperator extends Match { - public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MatchOperator", - MatchOperator::readFrom); + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( + Expression.class, + "MatchOperator", + MatchOperator::readFrom + ); @FunctionInfo( returnType = "boolean", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java index d9c3d68dda10a..76facd2631001 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLike.java @@ -58,7 +58,7 @@ Matching special characters (eg. `.`, `*`, `(`...) will require escaping. ---- include::{esql-specs}/string.csv-spec[tag=rlikeEscapingTripleQuotes] ---- - """, operator="RLIKE", examples = @Example(file = "docs", tag = "rlike")) + """, operator = "RLIKE", examples = @Example(file = "docs", tag = "rlike")) public RLike( Source source, @Param(name = "str", type = { "keyword", "text" }, description = "A literal value.") Expression value, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java index d5f411dd4d410..da0c0e47fbc52 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLike.java @@ -69,7 +69,7 @@ also act on a constant (literal) expression. The right-hand side of the operator ---- include::{esql-specs}/string.csv-spec[tag=likeEscapingTripleQuotes] ---- - """, operator="LIKE", examples = @Example(file = "docs", tag = "like")) + """, operator = "LIKE", examples = @Example(file = "docs", tag = "like")) public WildcardLike( Source source, @Param(name = "str", type = { "keyword", "text" }, description = "A literal expression.") Expression left, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index bf6d263a34302..d741aea43ac6a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -44,7 +44,6 @@ import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.evaluator.EvalMapper; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; -import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; From ee4bcb97ad7244d429e242a387e1a643a314375e Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Tue, 21 Jan 2025 12:34:09 +0100 Subject: [PATCH 6/9] Implement tested operations in MatchOperator --- .../esql/expression/function/fulltext/Match.java | 2 +- .../expression/function/fulltext/MatchOperator.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java index 7729cc60599bf..2cf480df03eb9 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java @@ -260,7 +260,7 @@ public Expression replaceChildren(List newChildren) { @Override protected NodeInfo info() { - return NodeInfo.create(this, Match::new, field, query(), queryBuilder()); + return NodeInfo.create(this, Match::new, field(), query(), queryBuilder()); } protected TypeResolutions.ParamOrdinal queryParamOrdinal() { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java index 1c4735ce4c177..1be443af544e3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; @@ -17,6 +18,7 @@ import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; import java.io.IOException; +import java.util.List; /** * This class exists to generate documentation for the match operator. @@ -74,4 +76,14 @@ protected static Match readFrom(StreamInput in) throws IOException { public String getWriteableName() { return ENTRY.name; } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, MatchOperator::new, field(), query()); + } + + @Override + public Expression replaceChildren(List newChildren) { + return new MatchOperator(source(), newChildren.get(0), newChildren.get(1)); + } } From a2b0f2939849e189499def7f8b20765f5d6e937a Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Wed, 22 Jan 2025 15:03:48 +0100 Subject: [PATCH 7/9] Fix visibility for private methods --- .../xpack/esql/expression/function/fulltext/Match.java | 2 +- .../xpack/esql/expression/function/fulltext/MatchOperator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java index 2cf480df03eb9..82f3c79eccf8a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java @@ -137,7 +137,7 @@ public Match(Source source, Expression field, Expression matchQuery, QueryBuilde this.field = field; } - protected static Match readFrom(StreamInput in) throws IOException { + private static Match readFrom(StreamInput in) throws IOException { Source source = Source.readFrom((PlanStreamInput) in); Expression field = in.readNamedWriteable(Expression.class); Expression query = in.readNamedWriteable(Expression.class); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java index 1be443af544e3..f7f61018de9fa 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -64,7 +64,7 @@ public MatchOperator( super(source, field, matchQuery); } - protected static Match readFrom(StreamInput in) throws IOException { + private static Match readFrom(StreamInput in) throws IOException { Source source = Source.readFrom((PlanStreamInput) in); Expression field = in.readNamedWriteable(Expression.class); Expression query = in.readNamedWriteable(Expression.class); From ed108863cad01d11199004ce37d3f1ec77d988ab Mon Sep 17 00:00:00 2001 From: carlosdelest Date: Wed, 22 Jan 2025 17:16:14 +0100 Subject: [PATCH 8/9] Refactor Match and MatchOperator to have a common base class, so we can focus on the differences --- .../AbstractMatchFullTextFunction.java | 219 +++++++++++ .../expression/function/fulltext/Match.java | 216 +---------- .../function/fulltext/MatchOperator.java | 13 +- .../xpack/esql/parser/ExpressionBuilder.java | 4 +- .../AbstractMatchFullTextFunctionTests.java | 365 ++++++++++++++++++ .../function/fulltext/MatchErrorTests.java | 2 +- .../function/fulltext/MatchOperatorTests.java | 2 +- .../function/fulltext/MatchTests.java | 348 +---------------- 8 files changed, 605 insertions(+), 564 deletions(-) create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunction.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunctionTests.java diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunction.java new file mode 100644 index 0000000000000..86f1f6e30108c --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunction.java @@ -0,0 +1,219 @@ +/* + * 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.expression.function.fulltext; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware; +import org.elasticsearch.xpack.esql.common.Failure; +import org.elasticsearch.xpack.esql.common.Failures; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.querydsl.query.Query; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; +import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction; +import org.elasticsearch.xpack.esql.planner.TranslatorHandler; +import org.elasticsearch.xpack.esql.querydsl.query.MatchQuery; +import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; +import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN; +import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; +import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS; +import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; +import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; +import static org.elasticsearch.xpack.esql.core.type.DataType.IP; +import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; +import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; +import static org.elasticsearch.xpack.esql.core.type.DataType.SEMANTIC_TEXT; +import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; +import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG; +import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION; +import static org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison.formatIncompatibleTypesMessage; + +/** + * This class contains the common functionalities between the match function ({@link Match}) and match operator ({@link MatchOperator}), + * so the two subclasses just contains the different code + */ +public abstract class AbstractMatchFullTextFunction extends FullTextFunction implements PostOptimizationVerificationAware { + public static final Set FIELD_DATA_TYPES = Set.of( + KEYWORD, + TEXT, + SEMANTIC_TEXT, + BOOLEAN, + DATETIME, + DATE_NANOS, + DOUBLE, + INTEGER, + IP, + LONG, + UNSIGNED_LONG, + VERSION + ); + public static final Set QUERY_DATA_TYPES = Set.of( + KEYWORD, + BOOLEAN, + DATETIME, + DATE_NANOS, + DOUBLE, + INTEGER, + IP, + LONG, + UNSIGNED_LONG, + VERSION + ); + protected final Expression field; + + protected AbstractMatchFullTextFunction( + Source source, + Expression query, + List children, + QueryBuilder queryBuilder, + Expression field + ) { + super(source, query, children, queryBuilder); + this.field = field; + } + + public Expression field() { + return field; + } + + @Override + protected TypeResolution resolveNonQueryParamTypes() { + return isNotNull(field, sourceText(), FIRST).and( + isType( + field, + FIELD_DATA_TYPES::contains, + sourceText(), + FIRST, + "keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version" + ) + ); + } + + @Override + protected TypeResolution resolveQueryParamType() { + return isType( + query(), + QUERY_DATA_TYPES::contains, + sourceText(), + queryParamOrdinal(), + "keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version" + ).and(isNotNullAndFoldable(query(), sourceText(), queryParamOrdinal())); + } + + @Override + protected TypeResolution checkParamCompatibility() { + DataType fieldType = field().dataType(); + DataType queryType = query().dataType(); + + // Field and query types should match. If the query is a string, then it can match any field type. + if ((fieldType == queryType) || (queryType == KEYWORD)) { + return TypeResolution.TYPE_RESOLVED; + } + + if (fieldType.isNumeric() && queryType.isNumeric()) { + // When doing an unsigned long query, field must be an unsigned long + if ((queryType == UNSIGNED_LONG && fieldType != UNSIGNED_LONG) == false) { + return TypeResolution.TYPE_RESOLVED; + } + } + + return new TypeResolution(formatIncompatibleTypesMessage(fieldType, queryType, sourceText())); + } + + @Override + public void postOptimizationVerification(Failures failures) { + Expression fieldExpression = field(); + // Field may be converted to other data type (field_name :: data_type), so we need to check the original field + if (fieldExpression instanceof AbstractConvertFunction convertFunction) { + fieldExpression = convertFunction.field(); + } + if (fieldExpression instanceof FieldAttribute == false) { + failures.add( + Failure.fail( + field, + "[{}] {} cannot operate on [{}], which is not a field from an index mapping", + functionName(), + functionType(), + field.sourceText() + ) + ); + } + } + + @Override + public Object queryAsObject() { + Object queryAsObject = query().fold(FoldContext.small() /* TODO remove me */); + + // Convert BytesRef to string for string-based values + if (queryAsObject instanceof BytesRef bytesRef) { + return switch (query().dataType()) { + case IP -> EsqlDataTypeConverter.ipToString(bytesRef); + case VERSION -> EsqlDataTypeConverter.versionToString(bytesRef); + default -> bytesRef.utf8ToString(); + }; + } + + // Converts specific types to the correct type for the query + if (query().dataType() == DataType.UNSIGNED_LONG) { + return NumericUtils.unsignedLongAsBigInteger((Long) queryAsObject); + } else if (query().dataType() == DataType.DATETIME && queryAsObject instanceof Long) { + // When casting to date and datetime, we get a long back. But Match query needs a date string + return EsqlDataTypeConverter.dateTimeToString((Long) queryAsObject); + } else if (query().dataType() == DATE_NANOS && queryAsObject instanceof Long) { + return EsqlDataTypeConverter.nanoTimeToString((Long) queryAsObject); + } + + return queryAsObject; + } + + @Override + protected Query translate(TranslatorHandler handler) { + Expression fieldExpression = field; + // Field may be converted to other data type (field_name :: data_type), so we need to check the original field + if (fieldExpression instanceof AbstractConvertFunction convertFunction) { + fieldExpression = convertFunction.field(); + } + if (fieldExpression instanceof FieldAttribute fieldAttribute) { + String fieldName = fieldAttribute.name(); + if (fieldAttribute.field() instanceof MultiTypeEsField multiTypeEsField) { + // If we have multiple field types, we allow the query to be done, but getting the underlying field name + fieldName = multiTypeEsField.getName(); + } + // Make query lenient so mixed field types can be queried when a field type is incompatible with the value provided + return new MatchQuery(source(), fieldName, queryAsObject(), Map.of("lenient", "true")); + } + + throw new IllegalArgumentException("Match must have a field attribute as the first argument"); + } + + @Override + public Expression replaceQueryBuilder(QueryBuilder queryBuilder) { + return new Match(source(), field, query(), queryBuilder); + } + + protected ParamOrdinal queryParamOrdinal() { + return SECOND; + } + +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java index 82f3c79eccf8a..ea5f3d9b83543 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java @@ -7,100 +7,31 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; -import org.apache.lucene.util.BytesRef; import org.elasticsearch.TransportVersions; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware; -import org.elasticsearch.xpack.esql.common.Failure; -import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; -import org.elasticsearch.xpack.esql.core.expression.FoldContext; -import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; -import org.elasticsearch.xpack.esql.core.querydsl.query.Query; -import org.elasticsearch.xpack.esql.core.querydsl.query.QueryStringQuery; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; -import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; -import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; -import org.elasticsearch.xpack.esql.planner.TranslatorHandler; -import org.elasticsearch.xpack.esql.querydsl.query.MatchQuery; -import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter; import java.io.IOException; import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; -import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN; -import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; -import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS; -import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; -import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; -import static org.elasticsearch.xpack.esql.core.type.DataType.IP; -import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; -import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; -import static org.elasticsearch.xpack.esql.core.type.DataType.SEMANTIC_TEXT; -import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; -import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG; -import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION; -import static org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison.formatIncompatibleTypesMessage; /** - * Full text function that performs a {@link QueryStringQuery} . - * Unlike other ESQL functions, Match is both a function and an operator. This class provides implementation for both, - * but the operator documentation is generated in {@link MatchOperator}. + * Full text function that performs a {@link org.elasticsearch.xpack.esql.querydsl.query.MatchQuery} . */ -public class Match extends FullTextFunction implements PostOptimizationVerificationAware { +public class Match extends AbstractMatchFullTextFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Match", Match::readFrom); - private final Expression field; - private transient Boolean isOperator; - public static final Set FIELD_DATA_TYPES = Set.of( - KEYWORD, - TEXT, - SEMANTIC_TEXT, - BOOLEAN, - DATETIME, - DATE_NANOS, - DOUBLE, - INTEGER, - IP, - LONG, - UNSIGNED_LONG, - VERSION - ); - public static final Set QUERY_DATA_TYPES = Set.of( - KEYWORD, - BOOLEAN, - DATETIME, - DATE_NANOS, - DOUBLE, - INTEGER, - IP, - LONG, - UNSIGNED_LONG, - VERSION - ); - @FunctionInfo( returnType = "boolean", preview = true, @@ -133,8 +64,7 @@ public Match( } public Match(Source source, Expression field, Expression matchQuery, QueryBuilder queryBuilder) { - super(source, matchQuery, List.of(field, matchQuery), queryBuilder); - this.field = field; + super(source, matchQuery, List.of(field, matchQuery), queryBuilder, field); } private static Match readFrom(StreamInput in) throws IOException { @@ -163,96 +93,6 @@ public String getWriteableName() { return ENTRY.name; } - @Override - protected TypeResolution resolveNonQueryParamTypes() { - return isNotNull(field, sourceText(), FIRST).and( - isType( - field, - FIELD_DATA_TYPES::contains, - sourceText(), - FIRST, - "keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version" - ) - ); - } - - @Override - protected TypeResolution resolveQueryParamType() { - return isType( - query(), - QUERY_DATA_TYPES::contains, - sourceText(), - queryParamOrdinal(), - "keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version" - ).and(isNotNullAndFoldable(query(), sourceText(), queryParamOrdinal())); - } - - @Override - protected TypeResolution checkParamCompatibility() { - DataType fieldType = field().dataType(); - DataType queryType = query().dataType(); - - // Field and query types should match. If the query is a string, then it can match any field type. - if ((fieldType == queryType) || (queryType == KEYWORD)) { - return TypeResolution.TYPE_RESOLVED; - } - - if (fieldType.isNumeric() && queryType.isNumeric()) { - // When doing an unsigned long query, field must be an unsigned long - if ((queryType == UNSIGNED_LONG && fieldType != UNSIGNED_LONG) == false) { - return TypeResolution.TYPE_RESOLVED; - } - } - - return new TypeResolution(formatIncompatibleTypesMessage(fieldType, queryType, sourceText())); - } - - @Override - public void postOptimizationVerification(Failures failures) { - Expression fieldExpression = field(); - // Field may be converted to other data type (field_name :: data_type), so we need to check the original field - if (fieldExpression instanceof AbstractConvertFunction convertFunction) { - fieldExpression = convertFunction.field(); - } - if (fieldExpression instanceof FieldAttribute == false) { - failures.add( - Failure.fail( - field, - "[{}] {} cannot operate on [{}], which is not a field from an index mapping", - functionName(), - functionType(), - field.sourceText() - ) - ); - } - } - - @Override - public Object queryAsObject() { - Object queryAsObject = query().fold(FoldContext.small() /* TODO remove me */); - - // Convert BytesRef to string for string-based values - if (queryAsObject instanceof BytesRef bytesRef) { - return switch (query().dataType()) { - case IP -> EsqlDataTypeConverter.ipToString(bytesRef); - case VERSION -> EsqlDataTypeConverter.versionToString(bytesRef); - default -> bytesRef.utf8ToString(); - }; - } - - // Converts specific types to the correct type for the query - if (query().dataType() == DataType.UNSIGNED_LONG) { - return NumericUtils.unsignedLongAsBigInteger((Long) queryAsObject); - } else if (query().dataType() == DataType.DATETIME && queryAsObject instanceof Long) { - // When casting to date and datetime, we get a long back. But Match query needs a date string - return EsqlDataTypeConverter.dateTimeToString((Long) queryAsObject); - } else if (query().dataType() == DATE_NANOS && queryAsObject instanceof Long) { - return EsqlDataTypeConverter.nanoTimeToString((Long) queryAsObject); - } - - return queryAsObject; - } - @Override public Expression replaceChildren(List newChildren) { return new Match(source(), newChildren.get(0), newChildren.get(1), queryBuilder()); @@ -262,54 +102,4 @@ public Expression replaceChildren(List newChildren) { protected NodeInfo info() { return NodeInfo.create(this, Match::new, field(), query(), queryBuilder()); } - - protected TypeResolutions.ParamOrdinal queryParamOrdinal() { - return SECOND; - } - - public Expression field() { - return field; - } - - @Override - public String functionType() { - return isOperator() ? "operator" : super.functionType(); - } - - @Override - protected Query translate(TranslatorHandler handler) { - Expression fieldExpression = field; - // Field may be converted to other data type (field_name :: data_type), so we need to check the original field - if (fieldExpression instanceof AbstractConvertFunction convertFunction) { - fieldExpression = convertFunction.field(); - } - if (fieldExpression instanceof FieldAttribute fieldAttribute) { - String fieldName = fieldAttribute.name(); - if (fieldAttribute.field() instanceof MultiTypeEsField multiTypeEsField) { - // If we have multiple field types, we allow the query to be done, but getting the underlying field name - fieldName = multiTypeEsField.getName(); - } - // Make query lenient so mixed field types can be queried when a field type is incompatible with the value provided - return new MatchQuery(source(), fieldName, queryAsObject(), Map.of("lenient", "true")); - } - - throw new IllegalArgumentException("Match must have a field attribute as the first argument"); - } - - @Override - public Expression replaceQueryBuilder(QueryBuilder queryBuilder) { - return new Match(source(), field, query(), queryBuilder); - } - - @Override - public String functionName() { - return isOperator() ? ":" : super.functionName(); - } - - private boolean isOperator() { - if (isOperator == null) { - isOperator = source().text().toUpperCase(Locale.ROOT).matches("^" + super.functionName() + "\\s*\\(.*\\)") == false; - } - return isOperator; - } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java index f7f61018de9fa..e3e4bc4678089 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperator.java @@ -21,8 +21,7 @@ import java.util.List; /** - * This class exists to generate documentation for the match operator. - * Unlike other ESQL functions, Match is both a function and an operator. This class provides the documentation for the operator. + * This class performs a {@link org.elasticsearch.xpack.esql.querydsl.query.MatchQuery} using an operator. */ public class MatchOperator extends Match { @@ -72,6 +71,16 @@ private static Match readFrom(StreamInput in) throws IOException { return new MatchOperator(source, field, query); } + @Override + public String functionType() { + return "operator"; + } + + @Override + public String functionName() { + return ":"; + } + @Override public String getWriteableName() { return ENTRY.name; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java index 283e305d79270..114fcda1e634a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java @@ -41,7 +41,7 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionResolutionStrategy; import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction; import org.elasticsearch.xpack.esql.expression.function.aggregate.FilteredExpression; -import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; +import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike; import org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLike; import org.elasticsearch.xpack.esql.expression.predicate.logical.And; @@ -988,6 +988,6 @@ public Expression visitMatchBooleanExpression(EsqlBaseParser.MatchBooleanExpress matchFieldExpression = expression(ctx.fieldExp); } - return new Match(source(ctx), matchFieldExpression, expression(ctx.matchQuery)); + return new MatchOperator(source(ctx), matchFieldExpression, expression(ctx.matchQuery)); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunctionTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunctionTests.java new file mode 100644 index 0000000000000..0a80da9c60625 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/AbstractMatchFullTextFunctionTests.java @@ -0,0 +1,365 @@ +/* + * 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.expression.function.fulltext; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; +import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.stringCases; +import static org.hamcrest.Matchers.equalTo; + +public abstract class AbstractMatchFullTextFunctionTests extends AbstractFunctionTestCase { + @ParametersFactory + public static Iterable parameters() { + List suppliers = new ArrayList<>(); + + AbstractMatchFullTextFunctionTests.addUnsignedLongCases(suppliers); + AbstractMatchFullTextFunctionTests.addNumericCases(suppliers); + AbstractMatchFullTextFunctionTests.addNonNumericCases(suppliers); + AbstractMatchFullTextFunctionTests.addQueryAsStringTestCases(suppliers); + AbstractMatchFullTextFunctionTests.addStringTestCases(suppliers); + + return parameterSuppliersFromTypedData(suppliers); + } + + private static void addNonNumericCases(List suppliers) { + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.booleanCases(), + TestCaseSupplier.booleanCases(), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ipCases(), + TestCaseSupplier.ipCases(), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.versionCases(""), + TestCaseSupplier.versionCases(""), + List.of(), + false + ) + ); + // Datetime + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.dateCases(), + TestCaseSupplier.dateCases(), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.dateNanosCases(), + TestCaseSupplier.dateNanosCases(), + List.of(), + false + ) + ); + } + + private static void addNumericCases(List suppliers) { + suppliers.addAll( + TestCaseSupplier.forBinaryComparisonWithWidening( + new TestCaseSupplier.NumericTypeTestConfigs<>( + new TestCaseSupplier.NumericTypeTestConfig<>( + (Integer.MIN_VALUE >> 1) - 1, + (Integer.MAX_VALUE >> 1) - 1, + (l, r) -> true, + "EqualsIntsEvaluator" + ), + new TestCaseSupplier.NumericTypeTestConfig<>( + (Long.MIN_VALUE >> 1) - 1, + (Long.MAX_VALUE >> 1) - 1, + (l, r) -> true, + "EqualsLongsEvaluator" + ), + new TestCaseSupplier.NumericTypeTestConfig<>( + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + // NB: this has different behavior than Double::equals + (l, r) -> true, + "EqualsDoublesEvaluator" + ) + ), + "field", + "query", + (lhs, rhs) -> List.of(), + false + ) + ); + } + + private static void addUnsignedLongCases(List suppliers) { + // TODO: These should be integrated into the type cross product above, but are currently broken + // see https://github.com/elastic/elasticsearch/issues/102935 + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + TestCaseSupplier.longCases(Long.MIN_VALUE, Long.MAX_VALUE, true), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + TestCaseSupplier.doubleCases(Double.MIN_VALUE, Double.MAX_VALUE, true), + List.of(), + false + ) + ); + } + + private static void addQueryAsStringTestCases(List suppliers) { + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.longCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.doubleCases(Double.MIN_VALUE, Double.MAX_VALUE, true), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + // Unsigned Long cases + // TODO: These should be integrated into the type cross product above, but are currently broken + // see https://github.com/elastic/elasticsearch/issues/102935 + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.booleanCases(), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.ipCases(), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.versionCases(""), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + // Datetime + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.dateCases(), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + + suppliers.addAll( + TestCaseSupplier.forBinaryNotCasting( + null, + "field", + "query", + Object::equals, + DataType.BOOLEAN, + TestCaseSupplier.dateNanosCases(), + TestCaseSupplier.stringCases(DataType.KEYWORD), + List.of(), + false + ) + ); + } + + private static void addStringTestCases(List suppliers) { + for (DataType fieldType : DataType.stringTypes()) { + if (DataType.UNDER_CONSTRUCTION.containsKey(fieldType)) { + continue; + } + for (TestCaseSupplier.TypedDataSupplier queryDataSupplier : stringCases(fieldType)) { + suppliers.add( + TestCaseSupplier.testCaseSupplier( + queryDataSupplier, + new TestCaseSupplier.TypedDataSupplier(fieldType.typeName(), () -> randomAlphaOfLength(10), DataType.KEYWORD), + (d1, d2) -> equalTo("string"), + DataType.BOOLEAN, + (o1, o2) -> true + ) + ); + } + } + } + + public final void testLiteralExpressions() { + Expression expression = buildLiteralExpression(testCase); + assertFalse("expected resolved", expression.typeResolved().unresolved()); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java index 1f4e8e40a8259..a83cb24a44a45 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java @@ -26,7 +26,7 @@ public class MatchErrorTests extends ErrorsForCasesWithoutExamplesTestCase { @Override protected List cases() { - return paramsToSuppliers(MatchTests.parameters()); + return paramsToSuppliers(AbstractMatchFullTextFunctionTests.parameters()); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java index 73205b18d472b..78ea3f5451880 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchOperatorTests.java @@ -30,7 +30,7 @@ public MatchOperatorTests(@Name("TestCase") Supplier @ParametersFactory public static Iterable parameters() { - return MatchTests.parameters(); + return AbstractMatchFullTextFunctionTests.parameters(); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java index cb0c9b263b547..2e707451b5df5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java @@ -8,26 +8,19 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; import com.carrotsearch.randomizedtesting.annotations.Name; + import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.core.util.NumericUtils; -import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.FunctionName; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; -import java.math.BigInteger; -import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; -import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.stringCases; -import static org.hamcrest.Matchers.equalTo; - @FunctionName("match") -public class MatchTests extends AbstractFunctionTestCase { +public class MatchTests extends AbstractMatchFullTextFunctionTests { public MatchTests(@Name("TestCase") Supplier testCaseSupplier) { this.testCase = testCaseSupplier.get(); @@ -35,342 +28,7 @@ public MatchTests(@Name("TestCase") Supplier testCase @ParametersFactory public static Iterable parameters() { - List suppliers = new ArrayList<>(); - - addUnsignedLongCases(suppliers); - addNumericCases(suppliers); - addNonNumericCases(suppliers); - addQueryAsStringTestCases(suppliers); - addStringTestCases(suppliers); - - return parameterSuppliersFromTypedData(suppliers); - } - - private static void addNonNumericCases(List suppliers) { - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.booleanCases(), - TestCaseSupplier.booleanCases(), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ipCases(), - TestCaseSupplier.ipCases(), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.versionCases(""), - TestCaseSupplier.versionCases(""), - List.of(), - false - ) - ); - // Datetime - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.dateCases(), - TestCaseSupplier.dateCases(), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.dateNanosCases(), - TestCaseSupplier.dateNanosCases(), - List.of(), - false - ) - ); - } - - private static void addNumericCases(List suppliers) { - suppliers.addAll( - TestCaseSupplier.forBinaryComparisonWithWidening( - new TestCaseSupplier.NumericTypeTestConfigs<>( - new TestCaseSupplier.NumericTypeTestConfig<>( - (Integer.MIN_VALUE >> 1) - 1, - (Integer.MAX_VALUE >> 1) - 1, - (l, r) -> true, - "EqualsIntsEvaluator" - ), - new TestCaseSupplier.NumericTypeTestConfig<>( - (Long.MIN_VALUE >> 1) - 1, - (Long.MAX_VALUE >> 1) - 1, - (l, r) -> true, - "EqualsLongsEvaluator" - ), - new TestCaseSupplier.NumericTypeTestConfig<>( - Double.NEGATIVE_INFINITY, - Double.POSITIVE_INFINITY, - // NB: this has different behavior than Double::equals - (l, r) -> true, - "EqualsDoublesEvaluator" - ) - ), - "field", - "query", - (lhs, rhs) -> List.of(), - false - ) - ); - } - - private static void addUnsignedLongCases(List suppliers) { - // TODO: These should be integrated into the type cross product above, but are currently broken - // see https://github.com/elastic/elasticsearch/issues/102935 - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - TestCaseSupplier.longCases(Long.MIN_VALUE, Long.MAX_VALUE, true), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - TestCaseSupplier.doubleCases(Double.MIN_VALUE, Double.MAX_VALUE, true), - List.of(), - false - ) - ); - } - - private static void addQueryAsStringTestCases(List suppliers) { - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.longCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.doubleCases(Double.MIN_VALUE, Double.MAX_VALUE, true), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - // Unsigned Long cases - // TODO: These should be integrated into the type cross product above, but are currently broken - // see https://github.com/elastic/elasticsearch/issues/102935 - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.booleanCases(), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.ipCases(), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.versionCases(""), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - // Datetime - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.dateCases(), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - - suppliers.addAll( - TestCaseSupplier.forBinaryNotCasting( - null, - "field", - "query", - Object::equals, - DataType.BOOLEAN, - TestCaseSupplier.dateNanosCases(), - TestCaseSupplier.stringCases(DataType.KEYWORD), - List.of(), - false - ) - ); - } - - private static void addStringTestCases(List suppliers) { - for (DataType fieldType : DataType.stringTypes()) { - if (DataType.UNDER_CONSTRUCTION.containsKey(fieldType)) { - continue; - } - for (TestCaseSupplier.TypedDataSupplier queryDataSupplier : stringCases(fieldType)) { - suppliers.add( - TestCaseSupplier.testCaseSupplier( - queryDataSupplier, - new TestCaseSupplier.TypedDataSupplier(fieldType.typeName(), () -> randomAlphaOfLength(10), DataType.KEYWORD), - (d1, d2) -> equalTo("string"), - DataType.BOOLEAN, - (o1, o2) -> true - ) - ); - } - } - } - - public final void testLiteralExpressions() { - Expression expression = buildLiteralExpression(testCase); - assertFalse("expected resolved", expression.typeResolved().unresolved()); + return AbstractMatchFullTextFunctionTests.parameters(); } @Override From fe2b78e0e01f6aa31b25cd079e4e1eb9700df231 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 22 Jan 2025 16:25:26 +0000 Subject: [PATCH 9/9] [CI] Auto commit changes from spotless --- .../xpack/esql/expression/function/fulltext/MatchTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java index 2e707451b5df5..4280ab487f213 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; import com.carrotsearch.randomizedtesting.annotations.Name; - import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.elasticsearch.xpack.esql.core.expression.Expression;