Match, Like and RLike operators improved docs (#120504)

This commit is contained in:
Carlos Delgado 2025-01-24 07:58:10 +01:00 committed by GitHub
parent 8fc5a507c5
commit f61f139653
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 721 additions and 578 deletions

View File

@ -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" : [

View File

@ -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 <<query-dsl-match-query,match query>> 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 <<text, text>> and <<semantic-text, semantic_text>>,\nas well as other field types like keyword, boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the <<esql-search-operators,match operator>> `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.",
"signatures" : [

View File

@ -3,7 +3,7 @@
"type" : "operator",
"operator" : ":",
"name" : "match_operator",
"description" : "Use `MATCH` to perform a <<query-dsl-match-query,match query>> 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 <<text, text>> and <<semantic-text, semantic_text>>,\nas well as other field types like keyword, boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the <<esql-search-operators,match operator>> `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.",
"description" : "Use the match operator (`:`) to perform a <<query-dsl-match-query,match query>> 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 <<esql-match,match function>>.\n\nFor using the function syntax, or adding <<match-field-params,match query parameters>>, you can use the\n<<esql-match,match function>>.\n\n`:` returns true if the provided query matches the row.",
"signatures" : [
{
"params" : [

View File

@ -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<<regexp-syntax,regular expressions>>. `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" : [

View File

@ -3,15 +3,15 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
-->
### MATCH_OPERATOR
Use `MATCH` to perform a <<query-dsl-match-query,match query>> 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 <<query-dsl-match-query,match query>> 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 <<text, text>> and <<semantic-text, semantic_text>>,
as well as other field types like keyword, boolean, dates, and numeric types.
The match operator is equivalent to the <<esql-match,match function>>.
For a simplified syntax, you can use the <<esql-search-operators,match operator>> `:` operator instead of `MATCH`.
For using the function syntax, or adding <<match-field-params,match query parameters>>, you can use the
<<esql-match,match function>>.
`MATCH` returns true if the provided query matches the row.
`:` returns true if the provided query matches the row.
```
FROM books

View File

@ -11,10 +11,13 @@ Returns true if the provided query matches the row.
The match operator is equivalent to the <<esql-match,match function>>.
For using the function syntax, or adding <<match-field-params,match query parameters>>, you can use the
<<esql-match,match function>>.
[.text-center]
image::esql/functions/signature/match_operator.svg[Embedded,opts=inline]
include::types/match.asciidoc[]
include::types/match_operator.asciidoc[]
[source.merge.styled,esql]
----

View File

@ -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<DataType> FIELD_DATA_TYPES = Set.of(
KEYWORD,
TEXT,
SEMANTIC_TEXT,
BOOLEAN,
DATETIME,
DATE_NANOS,
DOUBLE,
INTEGER,
IP,
LONG,
UNSIGNED_LONG,
VERSION
);
public static final Set<DataType> 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<Expression> 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;
}
}

View File

@ -25,6 +25,7 @@ public class FullTextWritables {
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()) {

View File

@ -7,101 +7,33 @@
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} .
* 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<DataType> FIELD_DATA_TYPES = Set.of(
KEYWORD,
TEXT,
SEMANTIC_TEXT,
BOOLEAN,
DATETIME,
DATE_NANOS,
DOUBLE,
INTEGER,
IP,
LONG,
UNSIGNED_LONG,
VERSION
);
public static final Set<DataType> QUERY_DATA_TYPES = Set.of(
KEYWORD,
BOOLEAN,
DATETIME,
DATE_NANOS,
DOUBLE,
INTEGER,
IP,
LONG,
UNSIGNED_LONG,
VERSION
);
@FunctionInfo(
returnType = "boolean",
operator = ":",
preview = true,
description = """
Use `MATCH` to perform a <<query-dsl-match-query,match query>> on the specified field.
@ -132,8 +64,7 @@ public class Match extends FullTextFunction implements PostOptimizationVerificat
}
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 {
@ -162,96 +93,6 @@ public class Match extends FullTextFunction implements PostOptimizationVerificat
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<Expression> newChildren) {
return new Match(source(), newChildren.get(0), newChildren.get(1), queryBuilder());
@ -259,56 +100,6 @@ public class Match extends FullTextFunction implements PostOptimizationVerificat
@Override
protected NodeInfo<? extends Expression> 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;
return NodeInfo.create(this, Match::new, field(), query(), queryBuilder());
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.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;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import java.io.IOException;
import java.util.List;
/**
* This class performs a {@link org.elasticsearch.xpack.esql.querydsl.query.MatchQuery} using an 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 <<query-dsl-match-query,match query>> on the specified field.
Using `:` is equivalent to using the `match` query in the Elasticsearch Query DSL.
The match operator is equivalent to the <<esql-match,match function>>.
For using the function syntax, or adding <<match-field-params,match query parameters>>, you can use the
<<esql-match,match function>>.
`:` 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);
}
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);
return new MatchOperator(source, field, query);
}
@Override
public String functionType() {
return "operator";
}
@Override
public String functionName() {
return ":";
}
@Override
public String getWriteableName() {
return ENTRY.name;
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, MatchOperator::new, field(), query());
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
return new MatchOperator(source(), newChildren.get(0), newChildren.get(1));
}
}

View File

@ -58,7 +58,7 @@ public class RLike extends org.elasticsearch.xpack.esql.core.expression.predicat
----
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,

View File

@ -69,7 +69,7 @@ public class WildcardLike extends org.elasticsearch.xpack.esql.core.expression.p
----
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,

View File

@ -41,7 +41,7 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
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 abstract class ExpressionBuilder extends IdentifierBuilder {
matchFieldExpression = expression(ctx.fieldExp);
}
return new Match(source(ctx), matchFieldExpression, expression(ctx.matchQuery));
return new MatchOperator(source(ctx), matchFieldExpression, expression(ctx.matchQuery));
}
}

View File

@ -44,7 +44,7 @@ import org.elasticsearch.xpack.esql.core.util.NumericUtils;
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;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike;
@ -139,7 +139,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();

View File

@ -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<Object[]> parameters() {
List<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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());
}
}

View File

@ -26,7 +26,7 @@ import static org.hamcrest.Matchers.equalTo;
public class MatchErrorTests extends ErrorsForCasesWithoutExamplesTestCase {
@Override
protected List<TestCaseSupplier> cases() {
return paramsToSuppliers(MatchTests.parameters());
return paramsToSuppliers(AbstractMatchFullTextFunctionTests.parameters());
}
@Override

View File

@ -10,9 +10,12 @@ 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.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
/**
@ -27,6 +30,11 @@ public class MatchOperatorTests extends MatchTests {
@ParametersFactory
public static Iterable<Object[]> parameters() {
return MatchTests.parameters();
return AbstractMatchFullTextFunctionTests.parameters();
}
@Override
protected Expression build(Source source, List<Expression> args) {
return new MatchOperator(source, args.get(0), args.get(1));
}
}

View File

@ -12,22 +12,14 @@ 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.TestCase> testCaseSupplier) {
this.testCase = testCaseSupplier.get();
@ -35,342 +27,7 @@ public class MatchTests extends AbstractFunctionTestCase {
@ParametersFactory
public static Iterable<Object[]> parameters() {
List<TestCaseSupplier> suppliers = new ArrayList<>();
addUnsignedLongCases(suppliers);
addNumericCases(suppliers);
addNonNumericCases(suppliers);
addQueryAsStringTestCases(suppliers);
addStringTestCases(suppliers);
return parameterSuppliersFromTypedData(suppliers);
}
private static void addNonNumericCases(List<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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<TestCaseSupplier> 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