Skip to content

Commit f61f139

Browse files
authored
Match, Like and RLike operators improved docs (#120504)
1 parent 8fc5a50 commit f61f139

File tree

18 files changed

+721
-578
lines changed

18 files changed

+721
-578
lines changed

docs/reference/esql/functions/kibana/definition/like.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/definition/match.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/definition/match_operator.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/definition/rlike.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/docs/match_operator.md

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/search.asciidoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ Returns true if the provided query matches the row.
1111

1212
The match operator is equivalent to the <<esql-match,match function>>.
1313

14+
For using the function syntax, or adding <<match-field-params,match query parameters>>, you can use the
15+
<<esql-match,match function>>.
16+
1417
[.text-center]
1518
image::esql/functions/signature/match_operator.svg[Embedded,opts=inline]
1619

17-
include::types/match.asciidoc[]
20+
include::types/match_operator.asciidoc[]
1821

1922
[source.merge.styled,esql]
2023
----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function.fulltext;
9+
10+
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.index.query.QueryBuilder;
12+
import org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware;
13+
import org.elasticsearch.xpack.esql.common.Failure;
14+
import org.elasticsearch.xpack.esql.common.Failures;
15+
import org.elasticsearch.xpack.esql.core.expression.Expression;
16+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
17+
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
18+
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
19+
import org.elasticsearch.xpack.esql.core.tree.Source;
20+
import org.elasticsearch.xpack.esql.core.type.DataType;
21+
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
22+
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
23+
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
24+
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
25+
import org.elasticsearch.xpack.esql.querydsl.query.MatchQuery;
26+
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
27+
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Set;
31+
32+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal;
33+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
34+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
35+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
36+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
37+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
38+
import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
39+
import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME;
40+
import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS;
41+
import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE;
42+
import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER;
43+
import static org.elasticsearch.xpack.esql.core.type.DataType.IP;
44+
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
45+
import static org.elasticsearch.xpack.esql.core.type.DataType.LONG;
46+
import static org.elasticsearch.xpack.esql.core.type.DataType.SEMANTIC_TEXT;
47+
import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT;
48+
import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG;
49+
import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION;
50+
import static org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison.formatIncompatibleTypesMessage;
51+
52+
/**
53+
* This class contains the common functionalities between the match function ({@link Match}) and match operator ({@link MatchOperator}),
54+
* so the two subclasses just contains the different code
55+
*/
56+
public abstract class AbstractMatchFullTextFunction extends FullTextFunction implements PostOptimizationVerificationAware {
57+
public static final Set<DataType> FIELD_DATA_TYPES = Set.of(
58+
KEYWORD,
59+
TEXT,
60+
SEMANTIC_TEXT,
61+
BOOLEAN,
62+
DATETIME,
63+
DATE_NANOS,
64+
DOUBLE,
65+
INTEGER,
66+
IP,
67+
LONG,
68+
UNSIGNED_LONG,
69+
VERSION
70+
);
71+
public static final Set<DataType> QUERY_DATA_TYPES = Set.of(
72+
KEYWORD,
73+
BOOLEAN,
74+
DATETIME,
75+
DATE_NANOS,
76+
DOUBLE,
77+
INTEGER,
78+
IP,
79+
LONG,
80+
UNSIGNED_LONG,
81+
VERSION
82+
);
83+
protected final Expression field;
84+
85+
protected AbstractMatchFullTextFunction(
86+
Source source,
87+
Expression query,
88+
List<Expression> children,
89+
QueryBuilder queryBuilder,
90+
Expression field
91+
) {
92+
super(source, query, children, queryBuilder);
93+
this.field = field;
94+
}
95+
96+
public Expression field() {
97+
return field;
98+
}
99+
100+
@Override
101+
protected TypeResolution resolveNonQueryParamTypes() {
102+
return isNotNull(field, sourceText(), FIRST).and(
103+
isType(
104+
field,
105+
FIELD_DATA_TYPES::contains,
106+
sourceText(),
107+
FIRST,
108+
"keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"
109+
)
110+
);
111+
}
112+
113+
@Override
114+
protected TypeResolution resolveQueryParamType() {
115+
return isType(
116+
query(),
117+
QUERY_DATA_TYPES::contains,
118+
sourceText(),
119+
queryParamOrdinal(),
120+
"keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"
121+
).and(isNotNullAndFoldable(query(), sourceText(), queryParamOrdinal()));
122+
}
123+
124+
@Override
125+
protected TypeResolution checkParamCompatibility() {
126+
DataType fieldType = field().dataType();
127+
DataType queryType = query().dataType();
128+
129+
// Field and query types should match. If the query is a string, then it can match any field type.
130+
if ((fieldType == queryType) || (queryType == KEYWORD)) {
131+
return TypeResolution.TYPE_RESOLVED;
132+
}
133+
134+
if (fieldType.isNumeric() && queryType.isNumeric()) {
135+
// When doing an unsigned long query, field must be an unsigned long
136+
if ((queryType == UNSIGNED_LONG && fieldType != UNSIGNED_LONG) == false) {
137+
return TypeResolution.TYPE_RESOLVED;
138+
}
139+
}
140+
141+
return new TypeResolution(formatIncompatibleTypesMessage(fieldType, queryType, sourceText()));
142+
}
143+
144+
@Override
145+
public void postOptimizationVerification(Failures failures) {
146+
Expression fieldExpression = field();
147+
// Field may be converted to other data type (field_name :: data_type), so we need to check the original field
148+
if (fieldExpression instanceof AbstractConvertFunction convertFunction) {
149+
fieldExpression = convertFunction.field();
150+
}
151+
if (fieldExpression instanceof FieldAttribute == false) {
152+
failures.add(
153+
Failure.fail(
154+
field,
155+
"[{}] {} cannot operate on [{}], which is not a field from an index mapping",
156+
functionName(),
157+
functionType(),
158+
field.sourceText()
159+
)
160+
);
161+
}
162+
}
163+
164+
@Override
165+
public Object queryAsObject() {
166+
Object queryAsObject = query().fold(FoldContext.small() /* TODO remove me */);
167+
168+
// Convert BytesRef to string for string-based values
169+
if (queryAsObject instanceof BytesRef bytesRef) {
170+
return switch (query().dataType()) {
171+
case IP -> EsqlDataTypeConverter.ipToString(bytesRef);
172+
case VERSION -> EsqlDataTypeConverter.versionToString(bytesRef);
173+
default -> bytesRef.utf8ToString();
174+
};
175+
}
176+
177+
// Converts specific types to the correct type for the query
178+
if (query().dataType() == DataType.UNSIGNED_LONG) {
179+
return NumericUtils.unsignedLongAsBigInteger((Long) queryAsObject);
180+
} else if (query().dataType() == DataType.DATETIME && queryAsObject instanceof Long) {
181+
// When casting to date and datetime, we get a long back. But Match query needs a date string
182+
return EsqlDataTypeConverter.dateTimeToString((Long) queryAsObject);
183+
} else if (query().dataType() == DATE_NANOS && queryAsObject instanceof Long) {
184+
return EsqlDataTypeConverter.nanoTimeToString((Long) queryAsObject);
185+
}
186+
187+
return queryAsObject;
188+
}
189+
190+
@Override
191+
protected Query translate(TranslatorHandler handler) {
192+
Expression fieldExpression = field;
193+
// Field may be converted to other data type (field_name :: data_type), so we need to check the original field
194+
if (fieldExpression instanceof AbstractConvertFunction convertFunction) {
195+
fieldExpression = convertFunction.field();
196+
}
197+
if (fieldExpression instanceof FieldAttribute fieldAttribute) {
198+
String fieldName = fieldAttribute.name();
199+
if (fieldAttribute.field() instanceof MultiTypeEsField multiTypeEsField) {
200+
// If we have multiple field types, we allow the query to be done, but getting the underlying field name
201+
fieldName = multiTypeEsField.getName();
202+
}
203+
// Make query lenient so mixed field types can be queried when a field type is incompatible with the value provided
204+
return new MatchQuery(source(), fieldName, queryAsObject(), Map.of("lenient", "true"));
205+
}
206+
207+
throw new IllegalArgumentException("Match must have a field attribute as the first argument");
208+
}
209+
210+
@Override
211+
public Expression replaceQueryBuilder(QueryBuilder queryBuilder) {
212+
return new Match(source(), field, query(), queryBuilder);
213+
}
214+
215+
protected ParamOrdinal queryParamOrdinal() {
216+
return SECOND;
217+
}
218+
219+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
2525
entries.add(MultiMatchQueryPredicate.ENTRY);
2626
entries.add(QueryString.ENTRY);
2727
entries.add(Match.ENTRY);
28+
entries.add(MatchOperator.ENTRY);
2829
entries.add(Kql.ENTRY);
2930

3031
if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) {

0 commit comments

Comments
 (0)