Skip to content

Commit 72344b9

Browse files
committed
Add match option types tests
1 parent b51e827 commit 72344b9

File tree

4 files changed

+119
-27
lines changed
  • docs/reference/esql/functions
  • x-pack/plugin/esql/src
    • main/java/org/elasticsearch/xpack/esql/expression/function/fulltext
    • test/java/org/elasticsearch/xpack/esql/expression/function/fulltext

4 files changed

+119
-27
lines changed

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

Lines changed: 25 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/types/match.asciidoc

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

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

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import java.util.List;
5151
import java.util.Locale;
5252
import java.util.Map;
53+
import java.util.Objects;
5354
import java.util.Set;
5455

5556
import static java.util.Map.entry;
@@ -173,11 +174,11 @@ public Match(
173174
) Expression matchQuery,
174175
@MapParam(
175176
params = {
176-
@MapParam.MapParamEntry(name = "analyzer", valueHint = { "standard" }),
177-
@MapParam.MapParamEntry(name = "auto_generate_synonyms_phrase_query", valueHint = { "true", "false" }),
178-
@MapParam.MapParamEntry(name = "fuzziness", valueHint = { "AUTO", "1", "2" }),
179-
@MapParam.MapParamEntry(name = "boost", valueHint = { "2.5" }),
180-
@MapParam.MapParamEntry(name = "fuzzy_transpositions", valueHint = { "true", "false" }),
177+
@MapParam.MapParamEntry(name = "analyzer", valueHint = { "standard" }, description = "Analyzer used to convert the text in the query value into token."),
178+
@MapParam.MapParamEntry(name = "auto_generate_synonyms_phrase_query", valueHint = { "true", "false" }, description = "If true, match phrase queries are automatically created for multi-term synonyms."),
179+
@MapParam.MapParamEntry(name = "fuzziness", valueHint = { "AUTO", "1", "2" }, description = "Maximum edit distance allowed for matching."),
180+
@MapParam.MapParamEntry(name = "boost", valueHint = { "2.5" }, description = "Floating point number used to decrease or increase the relevance scores of the query."),
181+
@MapParam.MapParamEntry(name = "fuzzy_transpositions", valueHint = { "true", "false" }, description = "If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba)."),
181182
@MapParam.MapParamEntry(
182183
name = "fuzzy_rewrite",
183184
valueHint = {
@@ -186,14 +187,14 @@ public Match(
186187
"constant_score_boolean",
187188
"top_terms_blended_freqs_N",
188189
"top_terms_boost_N",
189-
"top_terms_N" }
190+
"top_terms_N" }, description = "Method used to rewrite the query. See the rewrite parameter for valid values and more information."
190191
),
191-
@MapParam.MapParamEntry(name = "lenient", valueHint = { "true", "false" }),
192-
@MapParam.MapParamEntry(name = "max_expansions", valueHint = { "50" }),
193-
@MapParam.MapParamEntry(name = "minimum_should_match", valueHint = { "2" }),
194-
@MapParam.MapParamEntry(name = "operator", valueHint = { "AND", "OR" }),
195-
@MapParam.MapParamEntry(name = "prefix_length", valueHint = { "1" }),
196-
@MapParam.MapParamEntry(name = "zero_terms_query", valueHint = { "none", "all" }), },
192+
@MapParam.MapParamEntry(name = "lenient", valueHint = { "true", "false" }, description = "If false, format-based errors, such as providing a text query value for a numeric field, are not ignored."),
193+
@MapParam.MapParamEntry(name = "max_expansions", valueHint = { "50" }, description = "Maximum number of terms to which the query will expand."),
194+
@MapParam.MapParamEntry(name = "minimum_should_match", valueHint = { "2" }, description = "Minimum number of clauses that must match for a document to be returned."),
195+
@MapParam.MapParamEntry(name = "operator", valueHint = { "AND", "OR" }, description = "Boolean logic used to interpret text in the query value"),
196+
@MapParam.MapParamEntry(name = "prefix_length", valueHint = { "1" }, description = ""),
197+
@MapParam.MapParamEntry(name = "zero_terms_query", valueHint = { "none", "all" }, description = "Number of beginning characters left unchanged for fuzzy matching.")},
197198
description = "Match additional options. See <<query-dsl-match-query,match query>> for more information.",
198199
optional = true
199200
) Expression options
@@ -428,7 +429,7 @@ protected Query translate(TranslatorHandler handler) {
428429
fieldName = multiTypeEsField.getName();
429430
}
430431
// Make query lenient so mixed field types can be queried when a field type is incompatible with the value provided
431-
return new MatchQuery(source(), fieldName, queryAsObject(), optionsMap());
432+
return new MatchQuery(source(), fieldName, queryAsObject(), parseOptions());
432433
}
433434

434435
throw new IllegalArgumentException("Match must have a field attribute as the first argument");
@@ -451,7 +452,19 @@ private boolean isOperator() {
451452
return isOperator;
452453
}
453454

454-
public Map<String, Object> optionsMap() {
455-
return parseOptions();
455+
@Override
456+
public boolean equals(Object o) {
457+
// Match does not serialize options, as they get included in the query builder. We need to override equals and hashcode to
458+
// ignore options when comparing two Match functions
459+
if (o == null || getClass() != o.getClass()) return false;
460+
Match match = (Match) o;
461+
return Objects.equals(field(), match.field())
462+
&& Objects.equals(query(), match.query())
463+
&& Objects.equals(queryBuilder(), match.queryBuilder());
464+
}
465+
466+
@Override
467+
public int hashCode() {
468+
return Objects.hash(field(), query(), queryBuilder());
456469
}
457470
}

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
import com.carrotsearch.randomizedtesting.annotations.Name;
1111
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
1212

13+
import org.elasticsearch.index.query.QueryBuilder;
1314
import org.elasticsearch.xpack.esql.core.expression.Expression;
15+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
16+
import org.elasticsearch.xpack.esql.core.expression.Literal;
17+
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
1418
import org.elasticsearch.xpack.esql.core.tree.Source;
1519
import org.elasticsearch.xpack.esql.core.type.DataType;
1620
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
@@ -21,9 +25,13 @@
2125
import java.math.BigInteger;
2226
import java.util.ArrayList;
2327
import java.util.List;
28+
import java.util.Map;
2429
import java.util.function.Supplier;
2530

31+
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
32+
import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED;
2633
import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.stringCases;
34+
import static org.elasticsearch.xpack.esql.planner.TranslatorHandler.TRANSLATOR_HANDLER;
2735
import static org.hamcrest.Matchers.equalTo;
2836

2937
@FunctionName("match")
@@ -42,6 +50,7 @@ public static Iterable<Object[]> parameters() {
4250
addNonNumericCases(suppliers);
4351
addQueryAsStringTestCases(suppliers);
4452
addStringTestCases(suppliers);
53+
addMatchOptions(suppliers);
4554

4655
return parameterSuppliersFromTypedData(suppliers);
4756
}
@@ -215,7 +224,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
215224
Object::equals,
216225
DataType.BOOLEAN,
217226
TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true),
218-
TestCaseSupplier.stringCases(DataType.KEYWORD),
227+
TestCaseSupplier.stringCases(KEYWORD),
219228
List.of(),
220229
false
221230
)
@@ -229,7 +238,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
229238
Object::equals,
230239
DataType.BOOLEAN,
231240
TestCaseSupplier.intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true),
232-
TestCaseSupplier.stringCases(DataType.KEYWORD),
241+
TestCaseSupplier.stringCases(KEYWORD),
233242
List.of(),
234243
false
235244
)
@@ -243,7 +252,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
243252
Object::equals,
244253
DataType.BOOLEAN,
245254
TestCaseSupplier.longCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true),
246-
TestCaseSupplier.stringCases(DataType.KEYWORD),
255+
TestCaseSupplier.stringCases(KEYWORD),
247256
List.of(),
248257
false
249258
)
@@ -257,7 +266,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
257266
Object::equals,
258267
DataType.BOOLEAN,
259268
TestCaseSupplier.doubleCases(Double.MIN_VALUE, Double.MAX_VALUE, true),
260-
TestCaseSupplier.stringCases(DataType.KEYWORD),
269+
TestCaseSupplier.stringCases(KEYWORD),
261270
List.of(),
262271
false
263272
)
@@ -274,7 +283,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
274283
Object::equals,
275284
DataType.BOOLEAN,
276285
TestCaseSupplier.ulongCases(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX, true),
277-
TestCaseSupplier.stringCases(DataType.KEYWORD),
286+
TestCaseSupplier.stringCases(KEYWORD),
278287
List.of(),
279288
false
280289
)
@@ -288,7 +297,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
288297
Object::equals,
289298
DataType.BOOLEAN,
290299
TestCaseSupplier.booleanCases(),
291-
TestCaseSupplier.stringCases(DataType.KEYWORD),
300+
TestCaseSupplier.stringCases(KEYWORD),
292301
List.of(),
293302
false
294303
)
@@ -301,7 +310,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
301310
Object::equals,
302311
DataType.BOOLEAN,
303312
TestCaseSupplier.ipCases(),
304-
TestCaseSupplier.stringCases(DataType.KEYWORD),
313+
TestCaseSupplier.stringCases(KEYWORD),
305314
List.of(),
306315
false
307316
)
@@ -314,7 +323,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
314323
Object::equals,
315324
DataType.BOOLEAN,
316325
TestCaseSupplier.versionCases(""),
317-
TestCaseSupplier.stringCases(DataType.KEYWORD),
326+
TestCaseSupplier.stringCases(KEYWORD),
318327
List.of(),
319328
false
320329
)
@@ -328,7 +337,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
328337
Object::equals,
329338
DataType.BOOLEAN,
330339
TestCaseSupplier.dateCases(),
331-
TestCaseSupplier.stringCases(DataType.KEYWORD),
340+
TestCaseSupplier.stringCases(KEYWORD),
332341
List.of(),
333342
false
334343
)
@@ -342,7 +351,7 @@ private static void addQueryAsStringTestCases(List<TestCaseSupplier> suppliers)
342351
Object::equals,
343352
DataType.BOOLEAN,
344353
TestCaseSupplier.dateNanosCases(),
345-
TestCaseSupplier.stringCases(DataType.KEYWORD),
354+
TestCaseSupplier.stringCases(KEYWORD),
346355
List.of(),
347356
false
348357
)
@@ -358,7 +367,7 @@ private static void addStringTestCases(List<TestCaseSupplier> suppliers) {
358367
suppliers.add(
359368
TestCaseSupplier.testCaseSupplier(
360369
queryDataSupplier,
361-
new TestCaseSupplier.TypedDataSupplier(fieldType.typeName(), () -> randomAlphaOfLength(10), DataType.KEYWORD),
370+
new TestCaseSupplier.TypedDataSupplier(fieldType.typeName(), () -> randomAlphaOfLength(10), KEYWORD),
362371
(d1, d2) -> equalTo("string"),
363372
DataType.BOOLEAN,
364373
(o1, o2) -> true
@@ -368,13 +377,57 @@ private static void addStringTestCases(List<TestCaseSupplier> suppliers) {
368377
}
369378
}
370379

380+
private static void addMatchOptions(List<TestCaseSupplier> suppliers) {
381+
for (Map.Entry<String, DataType> allowedOptions : Match.ALLOWED_OPTIONS.entrySet()) {
382+
String optionName = allowedOptions.getKey();
383+
DataType optionType = allowedOptions.getValue();
384+
385+
suppliers.add(new TestCaseSupplier(List.of(KEYWORD, KEYWORD, UNSUPPORTED), () -> {
386+
Object optionValue = switch (optionType) {
387+
case BOOLEAN -> randomBoolean();
388+
case INTEGER -> randomIntBetween(0, 100000);
389+
case LONG -> randomLong();
390+
case FLOAT -> randomFloat();
391+
case DOUBLE -> randomDouble();
392+
case KEYWORD -> randomAlphaOfLength(10);
393+
default -> throw new IllegalArgumentException("Unsupported option type: " + optionType);
394+
};
395+
List<TestCaseSupplier.TypedData> values = new ArrayList<>();
396+
values.add(new TestCaseSupplier.TypedData(randomAlphaOfLength(10), KEYWORD, "field"));
397+
values.add(new TestCaseSupplier.TypedData(randomAlphaOfLength(10), KEYWORD, "query"));
398+
values.add(
399+
new TestCaseSupplier.TypedData(
400+
new MapExpression(
401+
Source.EMPTY,
402+
List.of(
403+
new Literal(Source.EMPTY, optionName, KEYWORD),
404+
new Literal(Source.EMPTY, optionValue, optionType)
405+
)
406+
),
407+
UNSUPPORTED,
408+
"options"
409+
).forceLiteral()
410+
);
411+
412+
return new TestCaseSupplier.TestCase(values, equalTo("MatchEvaluator"), DataType.BOOLEAN, equalTo(true));
413+
}));
414+
}
415+
}
416+
371417
public final void testLiteralExpressions() {
372418
Expression expression = buildLiteralExpression(testCase);
373419
assertFalse("expected resolved", expression.typeResolved().unresolved());
374420
}
375421

376422
@Override
377423
protected Expression build(Source source, List<Expression> args) {
378-
return new Match(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null);
424+
Match match = new Match(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null);
425+
// We need to add the QueryBuilder to the match expression, as it is used to implement equals() and hashCode() and
426+
// thus test the serialization methods. But we can only do this if the parameters make sense .
427+
if (args.get(0) instanceof FieldAttribute && args.get(1).foldable()) {
428+
QueryBuilder queryBuilder = TRANSLATOR_HANDLER.asQuery(match).asBuilder();
429+
match.replaceQueryBuilder(queryBuilder);
430+
}
431+
return match;
379432
}
380433
}

0 commit comments

Comments
 (0)