77
88package org .elasticsearch .xpack .esql .expression .function .fulltext ;
99
10+ import org .elasticsearch .ElasticsearchParseException ;
11+ import org .elasticsearch .TransportVersions ;
1012import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
1113import org .elasticsearch .common .io .stream .StreamInput ;
1214import org .elasticsearch .common .io .stream .StreamOutput ;
15+ import org .elasticsearch .common .unit .Fuzziness ;
1316import org .elasticsearch .xpack .esql .capabilities .Validatable ;
1417import org .elasticsearch .xpack .esql .common .Failure ;
1518import org .elasticsearch .xpack .esql .common .Failures ;
1922import org .elasticsearch .xpack .esql .core .querydsl .query .QueryStringQuery ;
2023import org .elasticsearch .xpack .esql .core .tree .NodeInfo ;
2124import org .elasticsearch .xpack .esql .core .tree .Source ;
25+ import org .elasticsearch .xpack .esql .core .type .DataType ;
2226import org .elasticsearch .xpack .esql .expression .function .Example ;
2327import org .elasticsearch .xpack .esql .expression .function .FunctionInfo ;
2428import org .elasticsearch .xpack .esql .expression .function .Param ;
29+ import org .elasticsearch .xpack .esql .expression .function .TwoOptionalArguments ;
2530import org .elasticsearch .xpack .esql .io .stream .PlanStreamInput ;
2631
2732import java .io .IOException ;
33+ import java .util .ArrayList ;
2834import java .util .List ;
2935
3036import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
37+ import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FOURTH ;
3138import 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 ;
3241import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isNotNull ;
42+ import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isNumeric ;
3343import 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