Skip to content

Commit 9d9318e

Browse files
committed
Add optional params to Match - fuzziness and boost
1 parent ffed86a commit 9d9318e

File tree

8 files changed

+391
-58
lines changed

8 files changed

+391
-58
lines changed

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

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

docs/reference/esql/functions/parameters/match.asciidoc

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

docs/reference/esql/functions/signature/match.svg

Lines changed: 1 addition & 1 deletion
Loading

docs/reference/esql/functions/types/match.asciidoc

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

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Expression.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public TypeResolution and(TypeResolution other) {
6565
return failed ? this : other;
6666
}
6767

68+
public TypeResolution or(TypeResolution other) {
69+
return resolved() ? this : other;
70+
}
71+
6872
public TypeResolution and(Supplier<TypeResolution> other) {
6973
return failed ? this : other.get();
7074
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,13 @@ protected TypeResolutions.ParamOrdinal queryParamOrdinal() {
105105
public Nullability nullable() {
106106
return Nullability.FALSE;
107107
}
108+
109+
/**
110+
* Used to differentiate error messages between functions and operators
111+
*
112+
* @return function type for error messages
113+
*/
114+
public String functionType() {
115+
return "function";
116+
}
108117
}

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

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
package org.elasticsearch.xpack.esql.expression.function.fulltext;
99

10+
import org.elasticsearch.ElasticsearchParseException;
11+
import org.elasticsearch.TransportVersions;
1012
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1113
import org.elasticsearch.common.io.stream.StreamInput;
1214
import org.elasticsearch.common.io.stream.StreamOutput;
15+
import org.elasticsearch.common.unit.Fuzziness;
1316
import org.elasticsearch.xpack.esql.capabilities.Validatable;
1417
import org.elasticsearch.xpack.esql.common.Failure;
1518
import org.elasticsearch.xpack.esql.common.Failures;
@@ -19,27 +22,38 @@
1922
import org.elasticsearch.xpack.esql.core.querydsl.query.QueryStringQuery;
2023
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
2124
import org.elasticsearch.xpack.esql.core.tree.Source;
25+
import org.elasticsearch.xpack.esql.core.type.DataType;
2226
import org.elasticsearch.xpack.esql.expression.function.Example;
2327
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
2428
import org.elasticsearch.xpack.esql.expression.function.Param;
29+
import org.elasticsearch.xpack.esql.expression.function.TwoOptionalArguments;
2530
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
2631

2732
import java.io.IOException;
33+
import java.util.ArrayList;
2834
import java.util.List;
2935

3036
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
37+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FOURTH;
3138
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
39+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.THIRD;
40+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
3241
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
42+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNumeric;
3343
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
44+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
3445

3546
/**
3647
* Full text function that performs a {@link QueryStringQuery} .
3748
*/
38-
public class Match extends FullTextFunction implements Validatable {
49+
public class Match extends FullTextFunction implements Validatable, TwoOptionalArguments {
3950

40-
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Match", Match::new);
51+
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Match", Match::readFrom);
4152

4253
private final Expression field;
54+
private final Expression boost;
55+
private final Expression fuzziness;
56+
private final boolean isOperator;
4357

4458
@FunctionInfo(
4559
returnType = "boolean",
@@ -54,21 +68,68 @@ public Match(
5468
name = "query",
5569
type = { "keyword", "text" },
5670
description = "Text you wish to find in the provided field."
57-
) Expression matchQuery
71+
) Expression matchQuery,
72+
@Param(
73+
optional = true,
74+
name = "boost",
75+
type = { "integer", "double" },
76+
description = "Boost value for the query."
77+
) Expression boost,
78+
@Param(optional = true, name = "boost", type = { "integer", "keyword" }, description = "Query fuzziness") Expression fuzziness
5879
) {
59-
super(source, matchQuery, List.of(field, matchQuery));
80+
this(source, field, matchQuery, boost, fuzziness, false);
81+
}
82+
83+
private Match(Source source, Expression field, Expression matchQuery, Expression boost, Expression fuzziness, boolean isOperator) {
84+
super(source, matchQuery, expressionList(field, matchQuery, boost, fuzziness));
6085
this.field = field;
86+
this.boost = boost;
87+
this.fuzziness = fuzziness;
88+
this.isOperator = isOperator;
89+
}
90+
91+
private static List<Expression> expressionList(Expression field, Expression matchQuery, Expression boost, Expression fuzziness) {
92+
List<Expression> list = new ArrayList<>(4);
93+
list.add(field);
94+
list.add(matchQuery);
95+
if (boost != null) {
96+
list.add(boost);
97+
}
98+
if (fuzziness != null) {
99+
list.add(fuzziness);
100+
}
101+
return list;
61102
}
62103

63-
private Match(StreamInput in) throws IOException {
64-
this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class));
104+
public static Match operator(Source source, Expression field, Expression matchQuery, Expression boost, Expression fuzziness) {
105+
return new Match(source, field, matchQuery, boost, fuzziness, true);
106+
}
107+
108+
private static Match readFrom(StreamInput in) throws IOException {
109+
Source source = Source.readFrom((PlanStreamInput) in);
110+
Expression field = in.readNamedWriteable(Expression.class);
111+
Expression query = in.readNamedWriteable(Expression.class);
112+
boolean isOperator = false;
113+
Expression boost = null;
114+
Expression fuzziness = null;
115+
if (in.getTransportVersion().onOrAfter(TransportVersions.MATCH_OPERATOR_FUZZINESS_BOOSTING)) {
116+
boost = in.readOptionalNamedWriteable(Expression.class);
117+
fuzziness = in.readOptionalNamedWriteable(Expression.class);
118+
isOperator = in.readBoolean();
119+
}
120+
return new Match(source, field, query, boost, fuzziness, isOperator);
65121
}
66122

67123
@Override
68124
public void writeTo(StreamOutput out) throws IOException {
69125
source().writeTo(out);
70-
out.writeNamedWriteable(field);
126+
out.writeNamedWriteable(field());
71127
out.writeNamedWriteable(query());
128+
if (out.getTransportVersion().onOrAfter(TransportVersions.MATCH_OPERATOR_FUZZINESS_BOOSTING)) {
129+
out.writeOptionalNamedWriteable(boost);
130+
out.writeOptionalNamedWriteable(fuzziness);
131+
out.writeBoolean(isOperator);
132+
}
72133
}
73134

74135
@Override
@@ -78,7 +139,23 @@ public String getWriteableName() {
78139

79140
@Override
80141
protected TypeResolution resolveNonQueryParamTypes() {
81-
return isNotNull(field, sourceText(), FIRST).and(isString(field, sourceText(), FIRST)).and(super.resolveNonQueryParamTypes());
142+
TypeResolution typeResolution = isNotNull(field, sourceText(), FIRST).and(isString(field, sourceText(), FIRST))
143+
.and(super.resolveNonQueryParamTypes());
144+
if (boost != null) {
145+
typeResolution = typeResolution.and(
146+
isNotNull(boost, sourceText(), THIRD).and(isNumeric(field, sourceText(), THIRD).and(isFoldable(field, sourceText(), THIRD)))
147+
);
148+
}
149+
if (fuzziness != null) {
150+
typeResolution = typeResolution.and(
151+
isNotNull(fuzziness, sourceText(), FOURTH).and(
152+
isType(fuzziness, dt -> dt == DataType.INTEGER, sourceText(), FOURTH, "integer,keyword").or(
153+
isString(fuzziness, sourceText(), FOURTH).and(isFoldable(fuzziness, sourceText(), FOURTH))
154+
)
155+
)
156+
);
157+
}
158+
return typeResolution;
82159
}
83160

84161
@Override
@@ -87,23 +164,34 @@ public void validate(Failures failures) {
87164
failures.add(
88165
Failure.fail(
89166
field,
90-
"[{}] cannot operate on [{}], which is not a field from an index mapping",
167+
"[{}] {} cannot operate on [{}], which is not a field from an index mapping",
91168
functionName(),
169+
functionType(),
92170
field.sourceText()
93171
)
94172
);
95173
}
174+
if (fuzziness != null) {
175+
try {
176+
fuzziness();
177+
} catch (IllegalArgumentException | ElasticsearchParseException e) {
178+
failures.add(
179+
Failure.fail(field, "Invalid fuzziness value [{}] for [{}] {}", fuzziness.sourceText(), functionName(), functionType())
180+
);
181+
}
182+
}
96183
}
97184

98185
@Override
99186
public Expression replaceChildren(List<Expression> newChildren) {
100-
// Query is the first child, field is the second child
101-
return new Match(source(), newChildren.get(0), newChildren.get(1));
187+
Expression boost = newChildren.size() > 2 ? newChildren.get(2) : null;
188+
Expression fuzziness = newChildren.size() > 3 ? newChildren.get(3) : null;
189+
return new Match(source(), newChildren.get(0), newChildren.get(1), boost, fuzziness, isOperator);
102190
}
103191

104192
@Override
105193
protected NodeInfo<? extends Expression> info() {
106-
return NodeInfo.create(this, Match::new, field, query());
194+
return NodeInfo.create(this, Match::new, field, query(), boost, fuzziness);
107195
}
108196

109197
protected TypeResolutions.ParamOrdinal queryParamOrdinal() {
@@ -113,4 +201,28 @@ protected TypeResolutions.ParamOrdinal queryParamOrdinal() {
113201
public Expression field() {
114202
return field;
115203
}
204+
205+
public Double boost() {
206+
if (boost == null) {
207+
return null;
208+
}
209+
return (Double) boost.fold();
210+
}
211+
212+
public Fuzziness fuzziness() {
213+
if (fuzziness == null) {
214+
return null;
215+
}
216+
return Fuzziness.fromString(fuzziness.fold().toString());
217+
}
218+
219+
@Override
220+
public String functionType() {
221+
return isOperator ? "operator" : super.functionType();
222+
}
223+
224+
@Override
225+
public String functionName() {
226+
return isOperator ? ":" : super.functionName();
227+
}
116228
}

0 commit comments

Comments
 (0)