Add multi_match function #121525 (#125062)

Implement multi_match function for ESQL. Its currently available on snapshot builds pending refinement of the syntax.
This commit is contained in:
Svilen Mihaylov 2025-04-15 09:38:08 -04:00 committed by GitHub
parent ac0972bd67
commit 02f9af732e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 2051 additions and 330 deletions

View File

@ -0,0 +1,5 @@
pr: 125062
summary: "Add initial version (behind snapshot) of `multi_match` function #121525"
area: Search
type: feature
issues: [121525]

View File

@ -0,0 +1,6 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
**Description**
Use `MULTI_MATCH` to perform a [multi-match query](/reference/query-languages/query-dsl/query-dsl-match-query.md#query-dsl-multi-match-query) on the specified field. The multi_match query builds on the match query to allow multi-field queries.

View File

@ -0,0 +1,31 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
**Examples**
```esql
FROM books
| WHERE MULTI_MATCH("Faulkner", author, description, {"fuzziness": 1})
| KEEP book_no, author
| SORT book_no
| LIMIT 5
```
| book_no:keyword | author:text |
| --- | --- |
| 2378 | [Carol Faulkner, Holly Byers Ochoa, Lucretia Mott] |
| 2713 | William Faulkner |
| 2847 | Colleen Faulkner |
| 2883 | William Faulkner |
| 3293 | Danny Faulkner |
```esql
FROM books
| WHERE MULTI_MATCH("Hobbit Back Again", title, description, {"operator": "AND"})
| KEEP title;
```
| title:text |
| --- |
| The Hobbit or There and Back Again |

View File

@ -0,0 +1,43 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
**Supported function named parameters**
`fuzziness`
: (keyword) Maximum edit distance allowed for matching.
`auto_generate_synonyms_phrase_query`
: (boolean) If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.
`minimum_should_match`
: (integer) Minimum number of clauses that must match for a document to be returned.
`fuzzy_transpositions`
: (boolean) If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.
`tie_breaker`
: (float) Controls how score is blended together between field groups. Defaults to 0 (best score from each group).
`type`
: (object) Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.
`lenient`
: (boolean) If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.
`operator`
: (keyword) Boolean logic used to interpret text in the query value. Defaults to OR.
`max_expansions`
: (integer) Maximum number of terms to which the query will expand. Defaults to 50.
`analyzer`
: (keyword) Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.
`boost`
: (float) Floating point number used to decrease or increase the relevance scores of the query.
`fuzzy_rewrite`
: (keyword) Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.
`prefix_length`
: (integer) Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.

View File

@ -0,0 +1,36 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
## `MULTI_MATCH` [esql-multi_match]
:::{warning}
Do not use on production environments. This functionality is in technical preview and
may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview
are not subject to the support SLA of official GA features.
:::
:::{note}
###### Serverless: GA, Elastic Stack: COMING 9.1.0
Support for optional named parameters is only available from 9.1.0
:::
**Syntax**
:::{image} ../../../images/functions/multi_match.svg
:alt: Embedded
:class: text-center
:::
:::{include} ../parameters/multi_match.md
:::
:::{include} ../description/multi_match.md
:::
:::{include} ../types/multi_match.md
:::
:::{include} ../functionNamedParams/multi_match.md
:::
:::{include} ../examples/multi_match.md
:::

View File

@ -0,0 +1,13 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
**Parameters**
`query`
: Value to find in the provided fields.
`fields`
: Fields to use for matching
`options`
: (Optional) Additional options for MultiMatch, passed as [function named parameters](/reference/query-languages/esql/esql-syntax.md#esql-function-named-params)." See [multi-match query](/reference/query-languages/query-dsl/query-dsl-match-query.md#query-dsl-multi-match-query) for more information.

View File

@ -0,0 +1,36 @@
% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
**Supported types**
| query | fields | options | result |
| --- | --- | --- | --- |
| boolean | boolean | named parameters | boolean |
| boolean | keyword | named parameters | boolean |
| date | date | named parameters | boolean |
| date | keyword | named parameters | boolean |
| date_nanos | date_nanos | named parameters | boolean |
| date_nanos | keyword | named parameters | boolean |
| double | double | named parameters | boolean |
| double | integer | named parameters | boolean |
| double | keyword | named parameters | boolean |
| double | long | named parameters | boolean |
| integer | double | named parameters | boolean |
| integer | integer | named parameters | boolean |
| integer | keyword | named parameters | boolean |
| integer | long | named parameters | boolean |
| ip | ip | named parameters | boolean |
| ip | keyword | named parameters | boolean |
| keyword | keyword | named parameters | boolean |
| long | double | named parameters | boolean |
| long | integer | named parameters | boolean |
| long | keyword | named parameters | boolean |
| long | long | named parameters | boolean |
| text | keyword | named parameters | boolean |
| unsigned_long | double | named parameters | boolean |
| unsigned_long | integer | named parameters | boolean |
| unsigned_long | keyword | named parameters | boolean |
| unsigned_long | long | named parameters | boolean |
| unsigned_long | unsigned_long | named parameters | boolean |
| version | keyword | named parameters | boolean |
| version | version | named parameters | boolean |

View File

@ -0,0 +1 @@
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="716" height="76" viewbox="0 0 716 76"><defs><style type="text/css">.c{fill:none;stroke:#222222;}.k{fill:#000000;font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:20px;}.s{fill:#e4f4ff;stroke:#222222;}.syn{fill:#8D8D8D;font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:20px;}</style></defs><path class="c" d="M0 46h5m152 0h10m32 0h10m80 0h30m-5 0q-5 0-5-5v-26q0-5 5-5h144q5 0 5 5v26q0 5-5 5m-107 0h10m92 0h30m32 0h30m104 0h20m-139 0q5 0 5 5v10q0 5 5 5h114q5 0 5-5v-10q0-5 5-5m5 0h10m32 0h5"/><rect class="s" x="5" y="20" width="152" height="36"/><text class="k" x="15" y="46">MULTI_MATCH</text><rect class="s" x="167" y="20" width="32" height="36" rx="7"/><text class="syn" x="177" y="46">(</text><rect class="s" x="209" y="20" width="80" height="36" rx="7"/><text class="k" x="219" y="46">query</text><rect class="s" x="319" y="20" width="32" height="36" rx="7"/><text class="syn" x="329" y="46">,</text><rect class="s" x="361" y="20" width="92" height="36" rx="7"/><text class="k" x="371" y="46">fields</text><rect class="s" x="483" y="20" width="32" height="36" rx="7"/><text class="syn" x="493" y="46">,</text><rect class="s" x="545" y="20" width="104" height="36" rx="7"/><text class="k" x="555" y="46">options</text><rect class="s" x="679" y="20" width="32" height="36" rx="7"/><text class="syn" x="689" y="46">)</text></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,739 @@
{
"comment" : "This is generated by ESQLs AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
"type" : "scalar",
"name" : "multi_match",
"description" : "Use `MULTI_MATCH` to perform a multi-match query on the specified field.\nThe multi_match query builds on the match query to allow multi-field queries.",
"signatures" : [
{
"params" : [
{
"name" : "query",
"type" : "boolean",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "boolean",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "boolean",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "date",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "date",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "date",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "date_nanos",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "date_nanos",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "date_nanos",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "double",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "double",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "double",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "integer",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "double",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "double",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "long",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "integer",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "double",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "integer",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "integer",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "integer",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "integer",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "long",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "ip",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "ip",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "ip",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "keyword",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "double",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "integer",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "long",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "text",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "unsigned_long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "double",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "unsigned_long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "integer",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "unsigned_long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "unsigned_long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "long",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "unsigned_long",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "unsigned_long",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "version",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "keyword",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
},
{
"params" : [
{
"name" : "query",
"type" : "version",
"optional" : false,
"description" : "Value to find in the provided fields."
},
{
"name" : "fields",
"type" : "version",
"optional" : false,
"description" : "Fields to use for matching"
},
{
"name" : "options",
"type" : "function_named_parameters",
"mapParams" : "{name='fuzziness', values=[AUTO, 1, 2], description='Maximum edit distance allowed for matching.'}, {name='auto_generate_synonyms_phrase_query', values=[true, false], description='If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.'}, {name='minimum_should_match', values=[2], description='Minimum number of clauses that must match for a document to be returned.'}, {name='fuzzy_transpositions', values=[true, false], description='If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.'}, {name='tie_breaker', values=[0], description='Controls how score is blended together between field groups. Defaults to 0 (best score from each group).'}, {name='type', values=['best_fields'], description='Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, `cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. See <<multi-match-types,multi_match types>>.'}, {name='lenient', values=[true, false], description='If false, format-based errors, such as providing a text query value for a numeric field, are returned. Defaults to true.'}, {name='operator', values=[AND, OR], description='Boolean logic used to interpret text in the query value. Defaults to OR.'}, {name='max_expansions', values=[50], description='Maximum number of terms to which the query will expand. Defaults to 50.'}, {name='analyzer', values=[standard], description='Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the indexs default analyzer is used.'}, {name='boost', values=[2.5], description='Floating point number used to decrease or increase the relevance scores of the query.'}, {name='fuzzy_rewrite', values=[constant_score_blended, constant_score, constant_score_boolean, top_terms_blended_freqs_N, top_terms_boost_N, top_terms_N], description='Method used to rewrite the query. See the rewrite parameter for valid values and more information. If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of top_terms_blended_freqs_${max_expansions} by default.'}, {name='prefix_length', values=[1], description='Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.'}",
"optional" : true,
"description" : "(Optional) Additional options for MultiMatch, passed as <<esql-function-named-params,function named parameters>>.\" See <<query-dsl-multi-match-query,multi-match query>> for more information."
}
],
"variadic" : true,
"returnType" : "boolean"
}
],
"examples" : [
"FROM books\n| WHERE MULTI_MATCH(\"Faulkner\", author, description, {\"fuzziness\": 1})\n| KEEP book_no, author\n| SORT book_no\n| LIMIT 5",
"FROM books\n| WHERE MULTI_MATCH(\"Hobbit Back Again\", title, description, {\"operator\": \"AND\"})\n| KEEP title;"
],
"preview" : true,
"snapshot_only" : false
}

View File

@ -0,0 +1,15 @@
<!--
This is generated by ESQLs AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
-->
### MULTI_MATCH
Use `MULTI_MATCH` to perform a [multi-match query](https://www.elastic.co/docs/reference/elasticsearch/query-languages/query-dsl/query-dsl-match-query#query-dsl-multi-match-query) on the specified field.
The multi_match query builds on the match query to allow multi-field queries.
```esql
FROM books
| WHERE MULTI_MATCH("Faulkner", author, description, {"fuzziness": 1})
| KEEP book_no, author
| SORT book_no
| LIMIT 5
```

View File

@ -53,21 +53,23 @@ public final class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatc
public static final ZeroTermsQueryOption DEFAULT_ZERO_TERMS_QUERY = MatchQueryParser.DEFAULT_ZERO_TERMS_QUERY; public static final ZeroTermsQueryOption DEFAULT_ZERO_TERMS_QUERY = MatchQueryParser.DEFAULT_ZERO_TERMS_QUERY;
public static final boolean DEFAULT_FUZZY_TRANSPOSITIONS = FuzzyQuery.defaultTranspositions; public static final boolean DEFAULT_FUZZY_TRANSPOSITIONS = FuzzyQuery.defaultTranspositions;
private static final ParseField SLOP_FIELD = new ParseField("slop"); public static final ParseField BOOST_FIELD = new ParseField("boost");
private static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query"); public static final ParseField SLOP_FIELD = new ParseField("slop");
private static final ParseField LENIENT_FIELD = new ParseField("lenient"); public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
private static final ParseField TIE_BREAKER_FIELD = new ParseField("tie_breaker"); public static final ParseField LENIENT_FIELD = new ParseField("lenient");
private static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite"); public static final ParseField TIE_BREAKER_FIELD = new ParseField("tie_breaker");
private static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match"); public static final ParseField FUZZINESS_FIELD = new ParseField("fuzziness");
private static final ParseField OPERATOR_FIELD = new ParseField("operator"); public static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite");
private static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions"); public static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match");
private static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length"); public static final ParseField OPERATOR_FIELD = new ParseField("operator");
private static final ParseField ANALYZER_FIELD = new ParseField("analyzer"); public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions");
private static final ParseField TYPE_FIELD = new ParseField("type"); public static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length");
private static final ParseField QUERY_FIELD = new ParseField("query"); public static final ParseField ANALYZER_FIELD = new ParseField("analyzer");
private static final ParseField FIELDS_FIELD = new ParseField("fields"); public static final ParseField TYPE_FIELD = new ParseField("type");
private static final ParseField GENERATE_SYNONYMS_PHRASE_QUERY = new ParseField("auto_generate_synonyms_phrase_query"); public static final ParseField QUERY_FIELD = new ParseField("query");
private static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions"); public static final ParseField FIELDS_FIELD = new ParseField("fields");
public static final ParseField GENERATE_SYNONYMS_PHRASE_QUERY = new ParseField("auto_generate_synonyms_phrase_query");
public static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
private final Object value; private final Object value;
private final Map<String, Float> fieldsBoosts; private final Map<String, Float> fieldsBoosts;

View File

@ -54,6 +54,7 @@ public abstract class LuceneQueryEvaluator<T extends Vector.Builder> implements
private final List<ShardState> perShardState; private final List<ShardState> perShardState;
protected LuceneQueryEvaluator(BlockFactory blockFactory, ShardConfig[] shards) { protected LuceneQueryEvaluator(BlockFactory blockFactory, ShardConfig[] shards) {
assert shards != null && shards.length > 0 : "LuceneQueryEvaluator requires shard information";
this.blockFactory = blockFactory; this.blockFactory = blockFactory;
this.shards = shards; this.shards = shards;
this.perShardState = new ArrayList<>(Collections.nCopies(shards.length, null)); this.perShardState = new ArrayList<>(Collections.nCopies(shards.length, null));

View File

@ -296,6 +296,7 @@ public final class CsvAssert {
width[c] = header(headers.get(c), types.get(c)).length(); width[c] = header(headers.get(c), types.get(c)).length();
} }
for (int r = 0; r < rows; r++) { for (int r = 0; r < rows; r++) {
assertThat("Mismatched header size and values", headers.size() == values.get(r).size());
for (int c = 0; c < headers.size(); c++) { for (int c = 0; c < headers.size(); c++) {
printableValues[r][c] = String.valueOf(valueTransformer.apply(types.get(c), values.get(r).get(c))); printableValues[r][c] = String.valueOf(valueTransformer.apply(types.get(c), values.get(r).get(c)));
width[c] = Math.max(width[c], printableValues[r][c].length()); width[c] = Math.max(width[c], printableValues[r][c].length());

View File

@ -0,0 +1,133 @@
###############################################
# Tests for MultiMatch function
#
multiMatchWithField
required_capability: multi_match_function
// tag::multi-match-with-field[]
FROM books
| WHERE MULTI_MATCH("Faulkner", author, description)
| KEEP book_no, author
| SORT book_no
| LIMIT 5
// end::multi-match-with-field[]
;
// tag::multi-match-with-field-result[]
book_no:keyword | author:text
2378 | [Carol Faulkner, Holly Byers Ochoa, Lucretia Mott]
2713 | William Faulkner
2847 | Colleen Faulkner
2883 | William Faulkner
3293 | Danny Faulkner
// end::multi-match-with-field-result[]
;
testMultiMatchWithOptionsFuzziness
required_capability: multi_match_function
from books
| where multi_match("Pings", title, description, {"fuzziness": 1})
| keep book_no;
ignoreOrder:true
book_no:keyword
1463
2675
2714
2936
4023
4917
5335
7140
7350
8875
;
testMultiMatchWithOptionsOperator
required_capability: multi_match_function
// tag::multi-match-with-named-function-params[]
FROM books
| WHERE MULTI_MATCH("Hobbit Back Again", title, description, {"operator": "AND"})
| KEEP title;
// end::multi-match-with-named-function-params[]
// tag::multi-match-with-named-function-params-result[]
title:text
The Hobbit or There and Back Again
// end::multi-match-with-named-function-params-result[]
;
testMultiMatchWithOptionsMinimumShouldMatch
required_capability: multi_match_function
from books
| where multi_match("here back again", title, description, {"minimum_should_match": 2, "operator": "OR"})
| sort book_no
| keep title;
title:text
My First 100 Words in Spanish/English (My First 100 Words Pull-Tab Book)
The Hobbit or There and Back Again
;
testMultiMatchWithNonPushableDisjunctions
required_capability: multi_match_function
required_capability: full_text_functions_disjunctions_compute_engine
from books
| where multi_match("lord", title, description) or length(title) > 130
| keep book_no
;
ignoreOrder: true
book_no:keyword
1463
2675
2714
2936
4023
5335
7140
7350
8678
8875
;
testMultiMatchPhraseQuery
required_capability: multi_match_function
from books
| where multi_match("Lord of the rings", title, description, { "type": "phrase" } )
| keep title
;
ignoreOrder: true
title:text
A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings
Return of the Shadow
The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1)
Letters of J R R Tolkien
The Lord of the Rings - Boxed Set
The Two Towers
Realms of Tolkien: Images of Middle-earth
Return of the King Being the Third Part of The Lord of the Rings
;
testMultiMatchPhrasePrefixQuery
required_capability: multi_match_function
from books
| where multi_match("Lord of the ri", title, { "type": "phrase_prefix" } )
| keep title
;
ignoreOrder: true
title:text
The Lord of the Rings - Boxed Set
Return of the King Being the Third Part of The Lord of the Rings
A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings
The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1)
;

View File

@ -114,6 +114,73 @@ book_no:keyword | title:text
6151 | Pop! Went Another Balloon: A Magical Counting Storybook (Magical Counting Storybooks) | [Keith Faulkner, Rory Tyger] | 5.975414276123047 6151 | Pop! Went Another Balloon: A Magical Counting Storybook (Magical Counting Storybooks) | [Keith Faulkner, Rory Tyger] | 5.975414276123047
; ;
testMultiMatchWithScore
required_capability: multi_match_function
required_capability: metadata_score
from books metadata _score
| where multi_match("Mark", author, title, {"fuzziness": 1})
| keep book_no, title, author, _score;
ignoreOrder:true
book_no:keyword | title:text | author:text | _score:double
2847 | To Love A Dark Stranger (Lovegram Historical Romance) | Colleen Faulkner | 1.9662091732025146
;
testMultiMatchWithScore1
required_capability: multi_match_function
required_capability: metadata_score
from books metadata _score
| where multi_match("Hobbit", description, title, {"type": "best_fields"})
| sort book_no
| eval _score = round(_score)
| keep book_no, _score;
ignoreOrder:true
book_no:keyword | _score:double
1463 | 2.0
2301 | 2.0
2675 | 2.0
2714 | 2.0
2936 | 1.0
4023 | 2.0
4289 | 3.0
5335 | 2.0
5996 | 2.0
6405 | 2.0
6760 | 2.0
7350 | 2.0
7480 | 3.0
;
testMultiMatchWithScore2
required_capability: multi_match_function
required_capability: metadata_score
from books metadata _score
| where multi_match("Hobbit", description, title, {"type": "most_fields"})
| sort book_no
| eval _score = round(_score)
| keep book_no, _score;
ignoreOrder:true
book_no:keyword | _score:double
1463 | 2.0
2301 | 2.0
2675 | 2.0
2714 | 2.0
2936 | 1.0
4023 | 2.0
4289 | 6.0
5335 | 2.0
5996 | 2.0
6405 | 2.0
6760 | 2.0
7350 | 2.0
7480 | 3.0
;
multipleWhereWithMatchScoringNoSort multipleWhereWithMatchScoringNoSort
required_capability: metadata_score required_capability: metadata_score
required_capability: match_operator_colon required_capability: match_operator_colon

View File

@ -46,6 +46,7 @@ public class ScoringIT extends AbstractEsqlIntegTestCase {
public static List<Object[]> params() { public static List<Object[]> params() {
List<Object[]> params = new ArrayList<>(); List<Object[]> params = new ArrayList<>();
params.add(new Object[] { "match(content, \"fox\")" }); params.add(new Object[] { "match(content, \"fox\")" });
params.add(new Object[] { "multi_match(\"fox\", content, {\"operator\": \"AND\"})" });
params.add(new Object[] { "content:\"fox\"" }); params.add(new Object[] { "content:\"fox\"" });
params.add(new Object[] { "qstr(\"content: fox\")" }); params.add(new Object[] { "qstr(\"content: fox\")" });
params.add(new Object[] { "kql(\"content*: fox\")" }); params.add(new Object[] { "kql(\"content*: fox\")" });

View File

@ -904,6 +904,11 @@ public class EsqlCapabilities {
*/ */
FULL_TEXT_FUNCTIONS_DISJUNCTIONS_SCORE, FULL_TEXT_FUNCTIONS_DISJUNCTIONS_SCORE,
/**
* Support for multi-match function.
*/
MULTI_MATCH_FUNCTION(Build.current().isSnapshot()),
/** /**
* Do {@code TO_LOWER} and {@code TO_UPPER} process all field values? * Do {@code TO_LOWER} and {@code TO_UPPER} process all field values?
*/ */

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.util.FeatureFlag; import org.elasticsearch.common.util.FeatureFlag;
import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException; import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
import org.elasticsearch.xpack.esql.core.expression.function.Function; import org.elasticsearch.xpack.esql.core.expression.function.Function;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataType;
@ -37,6 +38,7 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.Values;
import org.elasticsearch.xpack.esql.expression.function.aggregate.WeightedAvg; import org.elasticsearch.xpack.esql.expression.function.aggregate.WeightedAvg;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Kql; import org.elasticsearch.xpack.esql.expression.function.fulltext.Kql;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; import org.elasticsearch.xpack.esql.expression.function.fulltext.Match;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch;
import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Term; import org.elasticsearch.xpack.esql.expression.function.fulltext.Term;
import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket;
@ -428,6 +430,7 @@ public class EsqlFunctionRegistry {
new FunctionDefinition[] { new FunctionDefinition[] {
def(Kql.class, uni(Kql::new), "kql"), def(Kql.class, uni(Kql::new), "kql"),
def(Match.class, tri(Match::new), "match"), def(Match.class, tri(Match::new), "match"),
def(MultiMatch.class, MultiMatch::new, "multi_match"),
def(QueryString.class, bi(QueryString::new), "qstr") } }; def(QueryString.class, bi(QueryString::new), "qstr") } };
} }
@ -1025,6 +1028,32 @@ public class EsqlFunctionRegistry {
T build(Source source, Expression exp, List<Expression> variadic); T build(Source source, Expression exp, List<Expression> variadic);
} }
protected interface BinaryVariadicWithOptionsBuilder<T> {
T build(Source source, Expression exp, List<Expression> variadic, Expression options);
};
protected static <T extends Function> FunctionDefinition def(
Class<T> function,
BinaryVariadicWithOptionsBuilder<T> ctorRef,
String... names
) {
FunctionBuilder builder = (source, children, cfg) -> {
boolean hasMinimumOne = OptionalArgument.class.isAssignableFrom(function);
if (hasMinimumOne && children.size() < 1) {
throw new QlIllegalArgumentException("expects at least one argument");
} else if (hasMinimumOne == false && children.size() < 2) {
throw new QlIllegalArgumentException("expects at least two arguments");
}
Expression options = children.getLast();
if (options instanceof MapExpression) {
return ctorRef.build(source, children.get(0), children.subList(1, children.size() - 1), options);
}
return ctorRef.build(source, children.get(0), children.subList(1, children.size()), null);
};
return def(function, builder, names);
}
/** /**
* Build a {@linkplain FunctionDefinition} for a no-argument function that is configuration aware. * Build a {@linkplain FunctionDefinition} for a no-argument function that is configuration aware.
*/ */

View File

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.esql.expression.function.fulltext; package org.elasticsearch.xpack.esql.expression.function.fulltext;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.compute.lucene.LuceneQueryEvaluator.ShardConfig; import org.elasticsearch.compute.lucene.LuceneQueryEvaluator.ShardConfig;
import org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator; import org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator;
@ -17,14 +18,19 @@ import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
import org.elasticsearch.xpack.esql.capabilities.TranslationAware; import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.FoldContext;
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
import org.elasticsearch.xpack.esql.core.expression.Nullability; import org.elasticsearch.xpack.esql.core.expression.Nullability;
import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
import org.elasticsearch.xpack.esql.core.expression.function.Function; import org.elasticsearch.xpack.esql.core.expression.function.Function;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
import org.elasticsearch.xpack.esql.expression.predicate.logical.BinaryLogic; import org.elasticsearch.xpack.esql.expression.predicate.logical.BinaryLogic;
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not; import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
@ -42,12 +48,15 @@ import org.elasticsearch.xpack.esql.score.ExpressionScoreMapper;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
import static org.elasticsearch.xpack.esql.common.Failure.fail; import static org.elasticsearch.xpack.esql.common.Failure.fail;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
@ -200,6 +209,14 @@ public abstract class FullTextFunction extends Function
m -> "[" + m.functionName() + "] " + m.functionType(), m -> "[" + m.functionName() + "] " + m.functionType(),
failures failures
); );
checkCommandsBeforeExpression(
plan,
condition,
MultiMatch.class,
lp -> (lp instanceof Limit == false) && (lp instanceof Aggregate == false),
m -> "[" + m.functionName() + "] " + m.functionType(),
failures
);
checkCommandsBeforeExpression( checkCommandsBeforeExpression(
plan, plan,
condition, condition,
@ -315,4 +332,39 @@ public abstract class FullTextFunction extends Function
} }
return new LuceneQueryScoreEvaluator.Factory(shardConfigs); return new LuceneQueryScoreEvaluator.Factory(shardConfigs);
} }
protected static void populateOptionsMap(
final MapExpression options,
final Map<String, Object> optionsMap,
final TypeResolutions.ParamOrdinal paramOrdinal,
final String sourceText,
final Map<String, DataType> allowedOptions
) throws InvalidArgumentException {
for (EntryExpression entry : options.entryExpressions()) {
Expression optionExpr = entry.key();
Expression valueExpr = entry.value();
TypeResolution resolution = isFoldable(optionExpr, sourceText, paramOrdinal).and(
isFoldable(valueExpr, sourceText, paramOrdinal)
);
if (resolution.unresolved()) {
throw new InvalidArgumentException(resolution.message());
}
Object optionExprLiteral = ((Literal) optionExpr).value();
Object valueExprLiteral = ((Literal) valueExpr).value();
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
// validate the optionExpr is supported
DataType dataType = allowedOptions.get(optionName);
if (dataType == null) {
throw new InvalidArgumentException(
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, sourceText, allowedOptions.keySet())
);
}
try {
optionsMap.put(optionName, DataTypeConverter.convert(optionValue, dataType));
} catch (InvalidArgumentException e) {
throw new InvalidArgumentException(format(null, "Invalid option [{}] in [{}], {}", optionName, sourceText, e.getMessage()));
}
}
}
} }

View File

@ -9,7 +9,6 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.action.EsqlCapabilities;
import org.elasticsearch.xpack.esql.expression.predicate.fulltext.MultiMatchQueryPredicate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -20,9 +19,9 @@ public class FullTextWritables {
public static List<NamedWriteableRegistry.Entry> getNamedWriteables() { public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
List<NamedWriteableRegistry.Entry> entries = new ArrayList<>(); List<NamedWriteableRegistry.Entry> entries = new ArrayList<>();
entries.add(MultiMatchQueryPredicate.ENTRY);
entries.add(QueryString.ENTRY); entries.add(QueryString.ENTRY);
entries.add(Match.ENTRY); entries.add(Match.ENTRY);
entries.add(MultiMatch.ENTRY);
entries.add(Kql.ENTRY); entries.add(Kql.ENTRY);
if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) {

View File

@ -18,17 +18,14 @@ import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAwa
import org.elasticsearch.xpack.esql.common.Failure; import org.elasticsearch.xpack.esql.common.Failure;
import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException; import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.FoldContext;
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.expression.MapExpression; import org.elasticsearch.xpack.esql.core.expression.MapExpression;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
import org.elasticsearch.xpack.esql.core.util.Check; import org.elasticsearch.xpack.esql.core.util.Check;
import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.core.util.NumericUtils;
@ -55,7 +52,6 @@ import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
import static org.elasticsearch.index.query.AbstractQueryBuilder.BOOST_FIELD; import static org.elasticsearch.index.query.AbstractQueryBuilder.BOOST_FIELD;
import static org.elasticsearch.index.query.MatchQueryBuilder.ANALYZER_FIELD; import static org.elasticsearch.index.query.MatchQueryBuilder.ANALYZER_FIELD;
import static org.elasticsearch.index.query.MatchQueryBuilder.FUZZY_REWRITE_FIELD; import static org.elasticsearch.index.query.MatchQueryBuilder.FUZZY_REWRITE_FIELD;
@ -70,7 +66,6 @@ import static org.elasticsearch.index.query.MatchQueryBuilder.ZERO_TERMS_QUERY_F
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; 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.ParamOrdinal.SECOND;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.THIRD; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.THIRD;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; 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.isNotNullAndFoldable;
@ -374,7 +369,6 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna
} }
private Map<String, Object> matchQueryOptions() throws InvalidArgumentException { private Map<String, Object> matchQueryOptions() throws InvalidArgumentException {
if (options() == null) { if (options() == null) {
return Map.of(LENIENT_FIELD.getPreferredName(), true); return Map.of(LENIENT_FIELD.getPreferredName(), true);
} }
@ -383,33 +377,7 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna
// Match is lenient by default to avoid failing on incompatible types // Match is lenient by default to avoid failing on incompatible types
matchOptions.put(LENIENT_FIELD.getPreferredName(), true); matchOptions.put(LENIENT_FIELD.getPreferredName(), true);
for (EntryExpression entry : ((MapExpression) options()).entryExpressions()) { populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
Expression optionExpr = entry.key();
Expression valueExpr = entry.value();
TypeResolution resolution = isFoldable(optionExpr, sourceText(), SECOND).and(isFoldable(valueExpr, sourceText(), SECOND));
if (resolution.unresolved()) {
throw new InvalidArgumentException(resolution.message());
}
Object optionExprLiteral = ((Literal) optionExpr).value();
Object valueExprLiteral = ((Literal) valueExpr).value();
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
// validate the optionExpr is supported
DataType dataType = ALLOWED_OPTIONS.get(optionName);
if (dataType == null) {
throw new InvalidArgumentException(
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, sourceText(), ALLOWED_OPTIONS.keySet())
);
}
try {
matchOptions.put(optionName, DataTypeConverter.convert(optionValue, dataType));
} catch (InvalidArgumentException e) {
throw new InvalidArgumentException(
format(null, "Invalid option [{}] in [{}], {}", optionName, sourceText(), e.getMessage())
);
}
}
return matchOptions; return matchOptions;
} }
@ -492,16 +460,21 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna
protected Query translate(TranslatorHandler handler) { protected Query translate(TranslatorHandler handler) {
var fieldAttribute = fieldAsFieldAttribute(); var fieldAttribute = fieldAsFieldAttribute();
Check.notNull(fieldAttribute, "Match must have a field attribute as the first argument"); Check.notNull(fieldAttribute, "Match must have a field attribute as the first argument");
String fieldName = getNameFromFieldAttribute(fieldAttribute);
// 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(), matchQueryOptions());
}
public static String getNameFromFieldAttribute(FieldAttribute fieldAttribute) {
String fieldName = fieldAttribute.name(); String fieldName = fieldAttribute.name();
if (fieldAttribute.field() instanceof MultiTypeEsField multiTypeEsField) { 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 // If we have multiple field types, we allow the query to be done, but getting the underlying field name
fieldName = multiTypeEsField.getName(); fieldName = multiTypeEsField.getName();
} }
// Make query lenient so mixed field types can be queried when a field type is incompatible with the value provided return fieldName;
return new MatchQuery(source(), fieldName, queryAsObject(), matchQueryOptions());
} }
private FieldAttribute fieldAsFieldAttribute() { public static FieldAttribute fieldAsFieldAttribute(Expression field) {
Expression fieldExpression = field; Expression fieldExpression = field;
// Field may be converted to other data type (field_name :: data_type), so we need to check the original 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) { if (fieldExpression instanceof AbstractConvertFunction convertFunction) {
@ -510,6 +483,10 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna
return fieldExpression instanceof FieldAttribute fieldAttribute ? fieldAttribute : null; return fieldExpression instanceof FieldAttribute fieldAttribute ? fieldAttribute : null;
} }
private FieldAttribute fieldAsFieldAttribute() {
return fieldAsFieldAttribute(field);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
// Match does not serialize options, as they get included in the query builder. We need to override equals and hashcode to // Match does not serialize options, as they get included in the query builder. We need to override equals and hashcode to

View File

@ -0,0 +1,469 @@
/*
* 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.common.io.stream.StreamOutput;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
import org.elasticsearch.xpack.esql.common.Failure;
import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
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.util.Check;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.MapParam;
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
import org.elasticsearch.xpack.esql.querydsl.query.MultiMatchQuery;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import static java.util.Map.entry;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.ANALYZER_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.BOOST_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZINESS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZY_REWRITE_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZY_TRANSPOSITIONS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.GENERATE_SYNONYMS_PHRASE_QUERY;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.LENIENT_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.MAX_EXPANSIONS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.MINIMUM_SHOULD_MATCH_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.OPERATOR_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.PREFIX_LENGTH_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.SLOP_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.TIE_BREAKER_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.TYPE_FIELD;
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.ParamOrdinal.THIRD;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
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.FLOAT;
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.TEXT;
import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG;
import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION;
/**
* Full text function that performs a {@link org.elasticsearch.xpack.esql.querydsl.query.MultiMatchQuery} .
*/
public class MultiMatch extends FullTextFunction implements OptionalArgument, PostAnalysisPlanVerificationAware {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
Expression.class,
"MultiMatch",
MultiMatch::readFrom
);
public static final Set<DataType> QUERY_DATA_TYPES = Set.of(
KEYWORD,
BOOLEAN,
DATETIME,
DATE_NANOS,
DOUBLE,
INTEGER,
IP,
LONG,
UNSIGNED_LONG,
VERSION
);
public static final Set<DataType> FIELD_DATA_TYPES = Set.of(
KEYWORD,
TEXT,
BOOLEAN,
DATETIME,
DATE_NANOS,
DOUBLE,
INTEGER,
IP,
LONG,
UNSIGNED_LONG,
VERSION
);
public static final Map<String, DataType> OPTIONS = Map.ofEntries(
entry(BOOST_FIELD.getPreferredName(), FLOAT),
entry(SLOP_FIELD.getPreferredName(), INTEGER),
// TODO: add "zero_terms_query"
entry(ANALYZER_FIELD.getPreferredName(), KEYWORD),
entry(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), BOOLEAN),
entry(FUZZINESS_FIELD.getPreferredName(), KEYWORD),
entry(FUZZY_REWRITE_FIELD.getPreferredName(), KEYWORD),
entry(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), BOOLEAN),
entry(LENIENT_FIELD.getPreferredName(), BOOLEAN),
entry(MAX_EXPANSIONS_FIELD.getPreferredName(), INTEGER),
entry(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), KEYWORD),
entry(OPERATOR_FIELD.getPreferredName(), KEYWORD),
entry(PREFIX_LENGTH_FIELD.getPreferredName(), INTEGER),
entry(TIE_BREAKER_FIELD.getPreferredName(), FLOAT),
entry(TYPE_FIELD.getPreferredName(), KEYWORD)
);
@FunctionInfo(
returnType = "boolean",
preview = true,
description = """
Use `MULTI_MATCH` to perform a <<query-dsl-multi-match-query,multi-match query>> on the specified field.
The multi_match query builds on the match query to allow multi-field queries.""",
examples = {
@Example(file = "multi-match-function", tag = "multi-match-with-field"),
@Example(file = "multi-match-function", tag = "multi-match-with-named-function-params") },
appliesTo = {
@FunctionAppliesTo(
lifeCycle = FunctionAppliesToLifecycle.COMING,
version = "9.1.0",
description = "Support for optional named parameters is only available from 9.1.0"
) }
)
public MultiMatch(
Source source,
@Param(
name = "query",
type = { "keyword", "boolean", "date", "date_nanos", "double", "integer", "ip", "long", "text", "unsigned_long", "version" },
description = "Value to find in the provided fields."
) Expression query,
@Param(
name = "fields",
type = { "keyword", "boolean", "date", "date_nanos", "double", "integer", "ip", "long", "unsigned_long", "version" },
description = "Fields to use for matching"
) List<Expression> fields,
@MapParam(
name = "options",
params = {
@MapParam.MapParamEntry(
name = "boost",
type = "float",
valueHint = { "2.5" },
description = "Floating point number used to decrease or increase the relevance scores of the query."
),
@MapParam.MapParamEntry(
name = "analyzer",
type = "keyword",
valueHint = { "standard" },
description = "Analyzer used to convert the text in the query value into token. Defaults to the index-time analyzer"
+ " mapped for the field. If no analyzer is mapped, the indexs default analyzer is used."
),
@MapParam.MapParamEntry(
name = "auto_generate_synonyms_phrase_query",
type = "boolean",
valueHint = { "true", "false" },
description = "If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true."
),
@MapParam.MapParamEntry(
name = "fuzziness",
type = "keyword",
valueHint = { "AUTO", "1", "2" },
description = "Maximum edit distance allowed for matching."
),
@MapParam.MapParamEntry(
name = "fuzzy_rewrite",
type = "keyword",
valueHint = {
"constant_score_blended",
"constant_score",
"constant_score_boolean",
"top_terms_blended_freqs_N",
"top_terms_boost_N",
"top_terms_N" },
description = "Method used to rewrite the query. See the rewrite parameter for valid values and more information. "
+ "If the fuzziness parameter is not 0, the match query uses a fuzzy_rewrite method of "
+ "top_terms_blended_freqs_${max_expansions} by default."
),
@MapParam.MapParamEntry(
name = "fuzzy_transpositions",
type = "boolean",
valueHint = { "true", "false" },
description = "If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). "
+ "Defaults to true."
),
@MapParam.MapParamEntry(
name = "lenient",
type = "boolean",
valueHint = { "true", "false" },
description = "If false, format-based errors, such as providing a text query value for a numeric field, are returned. "
+ "Defaults to true."
),
@MapParam.MapParamEntry(
name = "max_expansions",
type = "integer",
valueHint = { "50" },
description = "Maximum number of terms to which the query will expand. Defaults to 50."
),
@MapParam.MapParamEntry(
name = "minimum_should_match",
type = "integer",
valueHint = { "2" },
description = "Minimum number of clauses that must match for a document to be returned."
),
@MapParam.MapParamEntry(
name = "operator",
type = "keyword",
valueHint = { "AND", "OR" },
description = "Boolean logic used to interpret text in the query value. Defaults to OR."
),
@MapParam.MapParamEntry(
name = "prefix_length",
type = "integer",
valueHint = { "1" },
description = "Number of beginning characters left unchanged for fuzzy matching. Defaults to 0."
),
@MapParam.MapParamEntry(
name = "tie_breaker",
type = "float",
valueHint = { "0" },
description = "Controls how score is blended together between field groups. Defaults to 0 (best score from each group)."
),
@MapParam.MapParamEntry(
name = "type",
type = "object",
valueHint = { "'best_fields'" },
description = "Controls the way multi_match is executed internally. Can be one of `best_fields`, `most_fields`, "
+ "`cross_fields`, `phrase`, `phrase_prefix` or `bool_prefix`. Defaults to 'best_fields'. "
+ "See <<multi-match-types,multi_match types>>."
), },
description = "(Optional) Additional options for MultiMatch, "
+ "passed as <<esql-function-named-params,function named parameters>>.\"\n"
+ " See <<query-dsl-multi-match-query,multi-match query>> for more information.",
optional = true
) Expression options
) {
this(source, query, fields, options, null);
}
// Due to current limitations, the options field may contain a field, in which case treat it as a field, and use "null" for actual
// options. We also remember the originally supplied arguments in order to make tests happy.
private final transient List<Expression> fields;
private final transient List<Expression> fieldsOriginal;
private final transient Expression options;
private final transient Expression optionsOriginal;
private static List<Expression> initChildren(Expression query, List<Expression> fields, Expression options) {
Stream<Expression> fieldsAndQuery = Stream.concat(Stream.of(query), fields.stream());
return (options == null ? fieldsAndQuery : Stream.concat(fieldsAndQuery, Stream.of(options))).toList();
}
private MultiMatch(Source source, Expression query, List<Expression> fields, Expression options, QueryBuilder queryBuilder) {
super(source, query, initChildren(query, fields, options), queryBuilder);
this.fieldsOriginal = fields;
this.optionsOriginal = options;
if (options == null || options instanceof MapExpression) {
this.fields = fields;
this.options = options;
} else {
this.fields = Stream.concat(fields.stream(), Stream.of(options)).toList();
this.options = null;
}
}
private static MultiMatch readFrom(StreamInput in) throws IOException {
Source source = Source.readFrom((PlanStreamInput) in);
Expression query = in.readNamedWriteable(Expression.class);
List<Expression> fields = in.readNamedWriteableCollectionAsList(Expression.class);
QueryBuilder queryBuilder = in.readOptionalNamedWriteable(QueryBuilder.class);
return new MultiMatch(source, query, fields, null, queryBuilder);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
source().writeTo(out);
out.writeNamedWriteable(query());
out.writeNamedWriteableCollection(fields);
out.writeOptionalNamedWriteable(queryBuilder());
}
@Override
public String getWriteableName() {
return ENTRY.name;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.getLast() instanceof MapExpression || newChildren.size() == children().size()) {
// if the last child is a MapExpression, it is the options map
return new MultiMatch(
source(),
newChildren.getFirst(),
newChildren.subList(1, newChildren.size() - 1),
newChildren.getLast(),
queryBuilder()
);
}
return new MultiMatch(source(), newChildren.getFirst(), newChildren.subList(1, newChildren.size()), null, queryBuilder());
}
@Override
protected NodeInfo<? extends Expression> info() {
// Specifically create new instance with original arguments.
return NodeInfo.create(this, MultiMatch::new, query(), fieldsOriginal, optionsOriginal);
}
@Override
protected Query translate(TranslatorHandler handler) {
Map<String, Float> fieldsWithBoost = new HashMap<>();
for (Expression field : fields) {
var fieldAttribute = Match.fieldAsFieldAttribute(field);
Check.notNull(fieldAttribute, "MultiMatch must have field attributes as arguments #2 to #N-1.");
String fieldName = Match.getNameFromFieldAttribute(fieldAttribute);
fieldsWithBoost.put(fieldName, 1.0f);
}
return new MultiMatchQuery(source(), Objects.toString(queryAsObject()), fieldsWithBoost, getOptions());
}
@Override
public Expression replaceQueryBuilder(QueryBuilder queryBuilder) {
// Specifically create new instance with original arguments.
return new MultiMatch(source(), query(), fieldsOriginal, optionsOriginal, queryBuilder);
}
public List<Expression> fields() {
return fields;
}
public Expression options() {
return options;
}
private Map<String, Object> getOptions() throws InvalidArgumentException {
Map<String, Object> options = new HashMap<>();
options.put(MatchQueryBuilder.LENIENT_FIELD.getPreferredName(), true);
if (options() == null) {
return options;
}
Match.populateOptionsMap((MapExpression) options(), options, THIRD, sourceText(), OPTIONS);
return options;
}
@Override
public String functionName() {
return ENTRY.name;
}
private TypeResolution resolveFields() {
return fields.stream()
.map(
(Expression field) -> isNotNull(field, sourceText(), FIRST).and(
isType(
field,
FIELD_DATA_TYPES::contains,
sourceText(),
SECOND,
"keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"
)
)
)
.reduce(TypeResolution::and)
.orElse(null);
}
private TypeResolution resolveOptions() {
if (options() != null) {
TypeResolution resolution = isNotNull(options(), sourceText(), THIRD);
if (resolution.unresolved()) {
return resolution;
}
// MapExpression does not have a DataType associated with it
resolution = isMapExpression(options(), sourceText(), THIRD);
if (resolution.unresolved()) {
return resolution;
}
try {
getOptions();
} catch (InvalidArgumentException e) {
return new TypeResolution(e.getMessage());
}
}
return TypeResolution.TYPE_RESOLVED;
}
private TypeResolution resolveQuery() {
return isType(
query(),
QUERY_DATA_TYPES::contains,
sourceText(),
FIRST,
"keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"
).and(isNotNullAndFoldable(query(), sourceText(), FIRST));
}
@Override
protected TypeResolution resolveParams() {
return resolveQuery().and(resolveFields()).and(resolveOptions());
}
@Override
public boolean equals(Object o) {
// MultiMatch does not serialize options, as they get included in the query builder. We need to override equals and hashcode to
// ignore options when comparing two MultiMatch functions
if (o == null || getClass() != o.getClass()) return false;
MultiMatch mm = (MultiMatch) o;
return Objects.equals(fields(), mm.fields())
&& Objects.equals(query(), mm.query())
&& Objects.equals(queryBuilder(), mm.queryBuilder());
}
@Override
public int hashCode() {
return Objects.hash(fields(), query(), queryBuilder());
}
@Override
public BiConsumer<LogicalPlan, Failures> postAnalysisPlanVerification() {
return (plan, failures) -> {
super.postAnalysisPlanVerification().accept(plan, failures);
plan.forEachExpression(MultiMatch.class, mm -> {
for (Expression field : fields) {
if (Match.fieldAsFieldAttribute(field) == null) {
failures.add(
Failure.fail(
field,
"[{}] {} cannot operate on [{}], which is not a field from an index mapping",
functionName(),
functionType(),
field.sourceText()
)
);
}
}
});
};
}
}

View File

@ -7,23 +7,19 @@
package org.elasticsearch.xpack.esql.expression.function.fulltext; package org.elasticsearch.xpack.esql.expression.function.fulltext;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.TransportVersions; import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException; import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
import org.elasticsearch.xpack.esql.core.expression.EntryExpression;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.expression.MapExpression; import org.elasticsearch.xpack.esql.core.expression.MapExpression;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
import org.elasticsearch.xpack.esql.core.querydsl.query.QueryStringQuery; import org.elasticsearch.xpack.esql.core.querydsl.query.QueryStringQuery;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
@ -42,7 +38,6 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
import static org.elasticsearch.index.query.QueryStringQueryBuilder.ALLOW_LEADING_WILDCARD_FIELD; import static org.elasticsearch.index.query.QueryStringQueryBuilder.ALLOW_LEADING_WILDCARD_FIELD;
import static org.elasticsearch.index.query.QueryStringQueryBuilder.ANALYZER_FIELD; import static org.elasticsearch.index.query.QueryStringQueryBuilder.ANALYZER_FIELD;
import static org.elasticsearch.index.query.QueryStringQueryBuilder.ANALYZE_WILDCARD_FIELD; import static org.elasticsearch.index.query.QueryStringQueryBuilder.ANALYZE_WILDCARD_FIELD;
@ -65,7 +60,6 @@ import static org.elasticsearch.index.query.QueryStringQueryBuilder.REWRITE_FIEL
import static org.elasticsearch.index.query.QueryStringQueryBuilder.TIME_ZONE_FIELD; import static org.elasticsearch.index.query.QueryStringQueryBuilder.TIME_ZONE_FIELD;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; 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.ParamOrdinal.SECOND;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; 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.isNotNullAndFoldable;
@ -330,33 +324,7 @@ public class QueryString extends FullTextFunction implements OptionalArgument {
} }
Map<String, Object> matchOptions = new HashMap<>(); Map<String, Object> matchOptions = new HashMap<>();
for (EntryExpression entry : ((MapExpression) options()).entryExpressions()) { populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS);
Expression optionExpr = entry.key();
Expression valueExpr = entry.value();
TypeResolution resolution = isFoldable(optionExpr, sourceText(), SECOND).and(isFoldable(valueExpr, sourceText(), SECOND));
if (resolution.unresolved()) {
throw new InvalidArgumentException(resolution.message());
}
Object optionExprLiteral = ((Literal) optionExpr).value();
Object valueExprLiteral = ((Literal) valueExpr).value();
String optionName = optionExprLiteral instanceof BytesRef br ? br.utf8ToString() : optionExprLiteral.toString();
String optionValue = valueExprLiteral instanceof BytesRef br ? br.utf8ToString() : valueExprLiteral.toString();
// validate the optionExpr is supported
DataType dataType = ALLOWED_OPTIONS.get(optionName);
if (dataType == null) {
throw new InvalidArgumentException(
format(null, "Invalid option [{}] in [{}], expected one of {}", optionName, sourceText(), ALLOWED_OPTIONS.keySet())
);
}
try {
matchOptions.put(optionName, DataTypeConverter.convert(optionValue, dataType));
} catch (InvalidArgumentException e) {
throw new InvalidArgumentException(
format(null, "Invalid option [{}] in [{}], {}", optionName, sourceText(), e.getMessage())
);
}
}
return matchOptions; return matchOptions;
} }

View File

@ -1,106 +0,0 @@
/*
* 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.predicate.fulltext;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
import org.elasticsearch.xpack.esql.querydsl.query.MultiMatchQuery;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static java.util.Collections.emptyList;
public class MultiMatchQueryPredicate extends FullTextPredicate implements TranslationAware {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
Expression.class,
"MultiMatchQueryPredicate",
MultiMatchQueryPredicate::new
);
private final String fieldString;
private final Map<String, Float> fields;
public MultiMatchQueryPredicate(Source source, String fieldString, String query, String options) {
super(source, query, options, emptyList());
this.fieldString = fieldString;
// inferred
this.fields = FullTextUtils.parseFields(fieldString, source);
}
MultiMatchQueryPredicate(StreamInput in) throws IOException {
super(in);
assert super.children().isEmpty();
fieldString = in.readString();
// inferred
this.fields = FullTextUtils.parseFields(fieldString, source());
}
@Override
protected NodeInfo<MultiMatchQueryPredicate> info() {
return NodeInfo.create(this, MultiMatchQueryPredicate::new, fieldString, query(), options());
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
throw new UnsupportedOperationException("this type of node doesn't have any children to replace");
}
public String fieldString() {
return fieldString;
}
public Map<String, Float> fields() {
return fields;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(fieldString);
}
@Override
public int hashCode() {
return Objects.hash(fieldString, super.hashCode());
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
MultiMatchQueryPredicate other = (MultiMatchQueryPredicate) obj;
return Objects.equals(fieldString, other.fieldString);
}
return false;
}
@Override
public String getWriteableName() {
return ENTRY.name;
}
@Override
public boolean translatable(LucenePushdownPredicates pushdownPredicates) {
return true; // needs update if we'll ever validate the fields
}
@Override
public Query asQuery(TranslatorHandler handler) {
return new MultiMatchQuery(source(), query(), fields(), this);
}
}

View File

@ -7,64 +7,74 @@
package org.elasticsearch.xpack.esql.querydsl.query; package org.elasticsearch.xpack.esql.querydsl.query;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.core.Booleans;
import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.expression.predicate.fulltext.MultiMatchQueryPredicate;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.TreeMap;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.ANALYZER_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.BOOST_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZINESS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZY_REWRITE_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.FUZZY_TRANSPOSITIONS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.GENERATE_SYNONYMS_PHRASE_QUERY;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.LENIENT_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.MAX_EXPANSIONS_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.MINIMUM_SHOULD_MATCH_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.OPERATOR_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.PREFIX_LENGTH_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.SLOP_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.TIE_BREAKER_FIELD;
import static org.elasticsearch.index.query.MultiMatchQueryBuilder.TYPE_FIELD;
public class MultiMatchQuery extends Query { public class MultiMatchQuery extends Query {
private static final Map<String, BiConsumer<MultiMatchQueryBuilder, String>> BUILDER_APPLIERS; private static final Map<String, BiConsumer<MultiMatchQueryBuilder, Object>> BUILDER_APPLIERS;
static { static {
// TODO: it'd be great if these could be constants instead of Strings, needs a core change to make the fields public first
BUILDER_APPLIERS = Map.ofEntries( BUILDER_APPLIERS = Map.ofEntries(
entry("slop", (qb, s) -> qb.slop(Integer.valueOf(s))), entry(BOOST_FIELD.getPreferredName(), (qb, obj) -> qb.boost((Float) obj)),
entry(SLOP_FIELD.getPreferredName(), (qb, obj) -> qb.slop((Integer) obj)),
// TODO: add zero terms query support, I'm not sure the best way to parse it yet... // TODO: add zero terms query support, I'm not sure the best way to parse it yet...
// appliers.put("zero_terms_query", (qb, s) -> qb.zeroTermsQuery(s)); // appliers.put("zero_terms_query", (qb, s) -> qb.zeroTermsQuery(s));
entry("analyzer", MultiMatchQueryBuilder::analyzer), entry(ANALYZER_FIELD.getPreferredName(), (qb, obj) -> qb.analyzer((String) obj)),
entry("auto_generate_synonyms_phrase_query", (qb, s) -> qb.autoGenerateSynonymsPhraseQuery(Booleans.parseBoolean(s))), entry(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), (qb, obj) -> qb.autoGenerateSynonymsPhraseQuery((Boolean) obj)),
entry("fuzziness", (qb, s) -> qb.fuzziness(Fuzziness.fromString(s))), entry(FUZZINESS_FIELD.getPreferredName(), (qb, obj) -> qb.fuzziness(Fuzziness.fromString((String) obj))),
entry("fuzzy_rewrite", MultiMatchQueryBuilder::fuzzyRewrite), entry(FUZZY_REWRITE_FIELD.getPreferredName(), (qb, obj) -> qb.fuzzyRewrite((String) obj)),
entry("fuzzy_transpositions", (qb, s) -> qb.fuzzyTranspositions(Booleans.parseBoolean(s))), entry(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), (qb, obj) -> qb.fuzzyTranspositions((Boolean) obj)),
entry("lenient", (qb, s) -> qb.lenient(Booleans.parseBoolean(s))), entry(LENIENT_FIELD.getPreferredName(), (qb, obj) -> qb.lenient((Boolean) obj)),
entry("max_expansions", (qb, s) -> qb.maxExpansions(Integer.valueOf(s))), entry(MAX_EXPANSIONS_FIELD.getPreferredName(), (qb, obj) -> qb.maxExpansions((Integer) obj)),
entry("minimum_should_match", MultiMatchQueryBuilder::minimumShouldMatch), entry(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), (qb, obj) -> qb.minimumShouldMatch((String) obj)),
entry("operator", (qb, s) -> qb.operator(Operator.fromString(s))), entry(OPERATOR_FIELD.getPreferredName(), (qb, obj) -> qb.operator(Operator.fromString((String) obj))),
entry("prefix_length", (qb, s) -> qb.prefixLength(Integer.valueOf(s))), entry(PREFIX_LENGTH_FIELD.getPreferredName(), (qb, obj) -> qb.prefixLength((Integer) obj)),
entry("tie_breaker", (qb, s) -> qb.tieBreaker(Float.valueOf(s))), entry(TIE_BREAKER_FIELD.getPreferredName(), (qb, obj) -> qb.tieBreaker((Float) obj)),
entry("type", MultiMatchQueryBuilder::type) entry(TYPE_FIELD.getPreferredName(), (qb, obj) -> qb.type((String) obj))
); );
} }
private final String query; private final String query;
private final Map<String, Float> fields; private final Map<String, Float> fields;
private final Map<String, String> options; private final Map<String, Object> options;
private final MultiMatchQueryPredicate predicate;
public MultiMatchQuery(Source source, String query, Map<String, Float> fields, MultiMatchQueryPredicate predicate) { public MultiMatchQuery(Source source, String query, Map<String, Float> fields, Map<String, Object> options) {
super(source); super(source);
this.query = query; this.query = query;
this.fields = fields; this.fields = fields;
this.predicate = predicate; this.options = options;
this.options = predicate.optionMap();
} }
@Override @Override
protected QueryBuilder asBuilder() { protected QueryBuilder asBuilder() {
final MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(query); final MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(query);
queryBuilder.fields(fields); queryBuilder.fields(fields);
queryBuilder.analyzer(predicate.analyzer());
options.forEach((k, v) -> { options.forEach((k, v) -> {
if (BUILDER_APPLIERS.containsKey(k)) { if (BUILDER_APPLIERS.containsKey(k)) {
BUILDER_APPLIERS.get(k).accept(queryBuilder, v); BUILDER_APPLIERS.get(k).accept(queryBuilder, v);
@ -77,7 +87,7 @@ public class MultiMatchQuery extends Query {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(query, fields, predicate); return Objects.hash(query, fields, options);
} }
@Override @Override
@ -91,11 +101,17 @@ public class MultiMatchQuery extends Query {
} }
MultiMatchQuery other = (MultiMatchQuery) obj; MultiMatchQuery other = (MultiMatchQuery) obj;
return Objects.equals(query, other.query) && Objects.equals(fields, other.fields) && Objects.equals(predicate, other.predicate); return Objects.equals(query, other.query) && Objects.equals(fields, other.fields) && Objects.equals(options, other.options);
} }
@Override @Override
protected String innerToString() { protected String innerToString() {
return fields + ":" + query; // Use a TreeMap so we get the fields in a predictable order.
return new TreeMap<>(fields) + ":" + query;
}
@Override
public boolean scorable() {
return true;
} }
} }

View File

@ -307,6 +307,10 @@ public class CsvTests extends ESTestCase {
"CSV tests cannot currently handle scoring that depends on Lucene", "CSV tests cannot currently handle scoring that depends on Lucene",
testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.METADATA_SCORE.capabilityName()) testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.METADATA_SCORE.capabilityName())
); );
assumeFalse(
"CSV tests cannot currently handle multi_match function that depends on Lucene",
testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.capabilityName())
);
if (Build.current().isSnapshot()) { if (Build.current().isSnapshot()) {
assertThat( assertThat(

View File

@ -45,6 +45,7 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.Max;
import org.elasticsearch.xpack.esql.expression.function.aggregate.Min; import org.elasticsearch.xpack.esql.expression.function.aggregate.Min;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; 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.fulltext.MatchOperator;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch;
import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring;
@ -2851,6 +2852,22 @@ public class AnalyzerTests extends ESTestCase {
assertEquals(DataType.DOUBLE, ee.dataType()); assertEquals(DataType.DOUBLE, ee.dataType());
} }
public void testFunctionNamedParamsAsFunctionArgument2() {
LogicalPlan plan = analyze("""
from test
| WHERE MULTI_MATCH("Anna Smith", first_name, last_name, {"minimum_should_match": 3.0})
""");
Limit limit = as(plan, Limit.class);
Filter filter = as(limit.child(), Filter.class);
MultiMatch mm = as(filter.condition(), MultiMatch.class);
MapExpression me = as(mm.options(), MapExpression.class);
assertEquals(1, me.entryExpressions().size());
EntryExpression ee = as(me.entryExpressions().get(0), EntryExpression.class);
assertEquals(new Literal(EMPTY, "minimum_should_match", DataType.KEYWORD), ee.key());
assertEquals(new Literal(EMPTY, 3.0, DataType.DOUBLE), ee.value());
assertEquals(DataType.DOUBLE, ee.dataType());
}
public void testResolveInsist_fieldExists_insistedOutputContainsNoUnmappedFields() { public void testResolveInsist_fieldExists_insistedOutputContainsNoUnmappedFields() {
assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled());

View File

@ -19,6 +19,7 @@ import org.elasticsearch.xpack.esql.core.type.EsField;
import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; import org.elasticsearch.xpack.esql.core.type.InvalidMappedField;
import org.elasticsearch.xpack.esql.core.type.UnsupportedEsField; import org.elasticsearch.xpack.esql.core.type.UnsupportedEsField;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; import org.elasticsearch.xpack.esql.expression.function.fulltext.Match;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch;
import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString;
import org.elasticsearch.xpack.esql.index.EsIndex; import org.elasticsearch.xpack.esql.index.EsIndex;
import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.index.IndexResolution;
@ -53,6 +54,7 @@ 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.IP;
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; 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.LONG;
import static org.elasticsearch.xpack.esql.core.type.DataType.OBJECT;
import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG; 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.core.type.DataType.VERSION;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -1465,6 +1467,7 @@ public class VerifierTests extends ESTestCase {
public void testFullTextFunctionsDisjunctions() { public void testFullTextFunctionsDisjunctions() {
checkWithFullTextFunctionsDisjunctions("match(last_name, \"Smith\")"); checkWithFullTextFunctionsDisjunctions("match(last_name, \"Smith\")");
checkWithFullTextFunctionsDisjunctions("multi_match(\"Smith\", first_name, last_name)");
checkWithFullTextFunctionsDisjunctions("last_name : \"Smith\""); checkWithFullTextFunctionsDisjunctions("last_name : \"Smith\"");
checkWithFullTextFunctionsDisjunctions("qstr(\"last_name: Smith\")"); checkWithFullTextFunctionsDisjunctions("qstr(\"last_name: Smith\")");
checkWithFullTextFunctionsDisjunctions("kql(\"last_name: Smith\")"); checkWithFullTextFunctionsDisjunctions("kql(\"last_name: Smith\")");
@ -2325,6 +2328,171 @@ public class VerifierTests extends ESTestCase {
); );
} }
public void testMultiMatchOptions() {
// Check positive cases
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name)");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, {\"analyzer\": \"standard\"})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"analyzer\": \"standard\"})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"slop\": 10})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"auto_generate_synonyms_phrase_query\": true})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"fuzziness\": 2})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"fuzzy_transpositions\": false})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"lenient\": false})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"max_expansions\": 10})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"minimum_should_match\": \"2\"})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"operator\": \"AND\"})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"prefix_length\": 2})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"tie_breaker\": 1.0})");
query("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"type\": \"best_fields\"})");
// Check all data types for available options
DataType[] optionTypes = new DataType[] { INTEGER, LONG, FLOAT, DOUBLE, KEYWORD, BOOLEAN };
for (Map.Entry<String, DataType> allowedOptions : MultiMatch.OPTIONS.entrySet()) {
String optionName = allowedOptions.getKey();
DataType optionType = allowedOptions.getValue();
// Check every possible type for the option - we'll try to convert it to the expected type
for (DataType currentType : optionTypes) {
String optionValue = switch (currentType) {
case BOOLEAN -> String.valueOf(randomBoolean());
case INTEGER -> String.valueOf(randomIntBetween(0, 100000));
case LONG -> String.valueOf(randomLong());
case FLOAT -> String.valueOf(randomFloat());
case DOUBLE -> String.valueOf(randomDouble());
case KEYWORD -> randomAlphaOfLength(10);
default -> throw new IllegalArgumentException("Unsupported option type: " + currentType);
};
String queryOptionValue = optionValue;
if (currentType == KEYWORD) {
queryOptionValue = "\"" + optionValue + "\"";
}
String query = "FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\""
+ optionName
+ "\": "
+ queryOptionValue
+ "})";
try {
// Check conversion is possible
DataTypeConverter.convert(optionValue, optionType);
// If no exception was thrown, conversion is possible and should be done
query(query);
} catch (InvalidArgumentException e) {
// Conversion is not possible, query should fail
assertEquals(
"1:19: Invalid option ["
+ optionName
+ "] in [MULTI_MATCH(\"Jean\", first_name, last_name, {\""
+ optionName
+ "\": "
+ queryOptionValue
+ "})], cannot "
+ (optionType == OBJECT ? "convert from" : "cast")
+ " ["
+ optionValue
+ "]"
+ (optionType == OBJECT ? (", type [keyword]") : "")
+ " to ["
+ optionType.typeName()
+ "]",
error(query)
);
}
}
}
assertThat(
error("FROM test | WHERE MULTI_MATCH(\"Jean\", first_name, last_name, {\"unknown_option\": true})"),
containsString(
"1:19: Invalid option [unknown_option] in [MULTI_MATCH(\"Jean\", first_name, last_name, "
+ "{\"unknown_option\": true})], expected one of "
)
);
}
public void testMultiMatchFunctionIsNotNullable() {
assertEquals(
"1:62: [MultiMatch] function cannot operate on [text::keyword], which is not a field from an index mapping",
error("row n = null | eval text = n + 5 | where multi_match(\"Anna\", text::keyword)")
);
}
public void testMultiMatchWithNonIndexedColumnCurrentlyUnsupported() {
assertEquals(
"1:78: [MultiMatch] function cannot operate on [initial], which is not a field from an index mapping",
error("from test | eval initial = substring(first_name, 1) | where multi_match(\"A\", initial)")
);
assertEquals(
"1:80: [MultiMatch] function cannot operate on [text], which is not a field from an index mapping",
error("from test | eval text=concat(first_name, last_name) | where multi_match(\"cat\", text)")
);
}
public void testMultiMatchFunctionNotAllowedAfterCommands() throws Exception {
assertEquals(
"1:24: [MultiMatch] function cannot be used after LIMIT",
error("from test | limit 10 | where multi_match(\"Anna\", first_name)")
);
assertEquals(
"1:47: [MultiMatch] function cannot be used after STATS",
error("from test | STATS c = AVG(salary) BY gender | where multi_match(\"F\", gender)")
);
}
public void testMultiMatchFunctionWithDisjunctions() {
checkWithDisjunctions("MultiMatch", "multi_match(\"Anna\", first_name, last_name)", "function");
}
public void testMultiMatchFunctionWithNonBooleanFunctions() {
checkFullTextFunctionsWithNonBooleanFunctions("MultiMatch", "multi_match(\"Anna\", first_name, last_name)", "function");
}
public void testMultiMatchFunctionArgNotConstant() throws Exception {
assertEquals(
"1:19: second argument of [match(first_name, first_name)] must be a constant, received [first_name]",
error("from test | where match(first_name, first_name)")
);
assertEquals(
"1:59: second argument of [match(first_name, query)] must be a constant, received [query]",
error("from test | eval query = concat(\"first\", \" name\") | where match(first_name, query)")
);
// Other value types are tested in QueryStringFunctionTests
}
// Should pass eventually once we lift some restrictions on the multi-match function.
public void testMultiMatchFunctionCurrentlyUnsupportedBehaviour() throws Exception {
assertEquals(
"1:82: Unknown column [first_name]\nline 1:94: Unknown column [last_name]",
error("from test | stats max_salary = max(salary) by emp_no | where multi_match(\"Anna\", first_name, last_name)")
);
}
public void testMultiMatchFunctionNullArgs() throws Exception {
assertEquals(
"1:19: first argument of [multi_match(\"query\", null)] cannot be null, received [null]",
error("from test | where multi_match(\"query\", null)")
);
assertEquals(
"1:19: first argument of [multi_match(null, first_name)] cannot be null, received [null]",
error("from test | where multi_match(null, first_name)")
);
}
public void testMultiMatchTargetsExistingField() throws Exception {
assertEquals(
"1:53: Unknown column [first_name]\nline 1:65: Unknown column [last_name]",
error("from test | keep emp_no | where multi_match(\"Anna\", first_name, last_name)")
);
}
public void testMultiMatchInsideEval() throws Exception {
assumeTrue("MultiMatch operator is available just for snapshots", Build.current().isSnapshot());
assertEquals(
"1:36: [MultiMatch] function is only supported in WHERE commands\n"
+ "line 1:55: [MultiMatch] function cannot operate on [title], which is not a field from an index mapping",
error("row title = \"brown fox\" | eval x = multi_match(\"fox\", title)")
);
}
public void testInsistNotOnTopOfFrom() { public void testInsistNotOnTopOfFrom() {
assumeTrue("requires snapshot builds", Build.current().isSnapshot()); assumeTrue("requires snapshot builds", Build.current().isSnapshot());

View File

@ -0,0 +1,46 @@
/*
* 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.Name;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
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;
import static org.elasticsearch.xpack.esql.planner.TranslatorHandler.TRANSLATOR_HANDLER;
/**
* This is essentially testing MultiMatch as if it was a regular match: it makes a singular list of the supplied "field" parameter.
*/
@FunctionName("multi_match")
public class MultiMatchTests extends MatchTests {
public MultiMatchTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
super(testCaseSupplier);
}
@Override
protected Expression build(Source source, List<Expression> args) {
// Note we are reversing the order of arguments here.
MultiMatch mm = new MultiMatch(source, args.get(1), List.of(args.get(0)), args.get(2));
// We need to add the QueryBuilder to the multi_match expression, as it is used to implement equals() and hashCode() and
// thus test the serialization methods. But we can only do this if the parameters make sense .
if (mm.query().foldable() && mm.fields().stream().allMatch(field -> field instanceof FieldAttribute)) {
QueryBuilder queryBuilder = TRANSLATOR_HANDLER.asQuery(mm).toQueryBuilder();
mm.replaceQueryBuilder(queryBuilder);
}
return mm;
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.predicate.fulltext;
import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class AbstractFulltextSerializationTests<T extends FullTextPredicate> extends AbstractExpressionSerializationTests<T> {
static final String OPTION_DELIMITER = ";";
String randomOptionOrNull() {
if (randomBoolean()) {
return null;
}
HashMap<String, String> options = new HashMap<>();
int maxOptions = randomInt(8);
for (int i = 0; i < maxOptions; i++) {
var opt = randomIndividualOption();
options.computeIfAbsent(opt.getKey(), k -> opt.getValue()); // no duplicate options
}
return options.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(OPTION_DELIMITER));
}
Map.Entry<String, String> randomIndividualOption() {
return Map.entry(randomAlphaOfLength(randomIntBetween(1, 4)), randomAlphaOfLength(randomIntBetween(1, 4)));
}
}

View File

@ -1,56 +0,0 @@
/*
* 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.predicate.fulltext;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MultiMatchQuerySerializationTests extends AbstractFulltextSerializationTests<MultiMatchQueryPredicate> {
@Override
protected final MultiMatchQueryPredicate createTestInstance() {
return new MultiMatchQueryPredicate(
randomSource(),
randomFieldString(),
randomAlphaOfLength(randomIntBetween(1, 16)),
randomOptionOrNull()
);
}
@Override
protected MultiMatchQueryPredicate mutateInstance(MultiMatchQueryPredicate instance) throws IOException {
var fieldString = instance.fieldString();
var query = instance.query();
var options = instance.options();
switch (between(0, 2)) {
case 0 -> fieldString = randomValueOtherThan(fieldString, this::randomFieldString);
case 1 -> query = randomValueOtherThan(query, () -> randomAlphaOfLength(randomIntBetween(1, 16)));
case 2 -> options = randomValueOtherThan(options, this::randomOptionOrNull);
}
return new MultiMatchQueryPredicate(instance.source(), fieldString, query, options);
}
String randomFieldString() {
if (randomBoolean()) {
return ""; // empty, no fields
}
HashMap<String, Float> fields = new HashMap<>();
int maxOptions = randomInt(4);
for (int i = 0; i < maxOptions; i++) {
var opt = randomIndividualField();
fields.computeIfAbsent(opt.getKey(), k -> opt.getValue()); // no duplicate fields
}
return fields.entrySet().stream().map(e -> e.getKey() + "^" + e.getValue()).collect(Collectors.joining(","));
}
Map.Entry<String, Float> randomIndividualField() {
return Map.entry(randomAlphaOfLength(randomIntBetween(1, 4)), randomFloat());
}
}

View File

@ -20,6 +20,7 @@ import org.elasticsearch.index.mapper.MapperServiceTestCase;
import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.ParsedDocument;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
@ -1657,6 +1658,37 @@ public class LocalPhysicalPlanOptimizerTests extends MapperServiceTestCase {
assertThat(expectedQStrQuery.toString(), is(planStr.get())); assertThat(expectedQStrQuery.toString(), is(planStr.get()));
} }
public void testMultiMatchOptionsPushDown() {
String query = """
from test
| where MULTI_MATCH("Anna", first_name, last_name, {"fuzzy_rewrite": "constant_score", "slop": 10, "analyzer": "auto",
"auto_generate_synonyms_phrase_query": "false", "fuzziness": "auto", "fuzzy_transpositions": false, "lenient": "false",
"max_expansions": 10, "minimum_should_match": 3, "operator": "AND", "prefix_length": 20, "tie_breaker": 1.0,
"type": "best_fields", "boost": 2.0})
""";
var plan = plannerOptimizer.plan(query);
AtomicReference<String> planStr = new AtomicReference<>();
plan.forEachDown(EsQueryExec.class, result -> planStr.set(result.query().toString()));
var expectedQuery = new MultiMatchQueryBuilder("Anna").fields(Map.of("first_name", 1.0f, "last_name", 1.0f))
.slop(10)
.boost(2.0f)
.analyzer("auto")
.autoGenerateSynonymsPhraseQuery(false)
.operator(Operator.fromString("AND"))
.fuzziness(Fuzziness.fromString("auto"))
.fuzzyRewrite("constant_score")
.fuzzyTranspositions(false)
.lenient(false)
.type("best_fields")
.maxExpansions(10)
.minimumShouldMatch("3")
.prefixLength(20)
.tieBreaker(1.0f);
assertThat(expectedQuery.toString(), is(planStr.get()));
}
/** /**
* Expecting * Expecting
* LimitExec[1000[INTEGER]] * LimitExec[1000[INTEGER]]

View File

@ -61,6 +61,7 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.Sum;
import org.elasticsearch.xpack.esql.expression.function.aggregate.ToPartial; import org.elasticsearch.xpack.esql.expression.function.aggregate.ToPartial;
import org.elasticsearch.xpack.esql.expression.function.aggregate.Values; import org.elasticsearch.xpack.esql.expression.function.aggregate.Values;
import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; import org.elasticsearch.xpack.esql.expression.function.fulltext.Match;
import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch;
import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket;
import org.elasticsearch.xpack.esql.expression.function.grouping.Categorize; import org.elasticsearch.xpack.esql.expression.function.grouping.Categorize;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble; import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble;
@ -7378,6 +7379,24 @@ public class LogicalPlanOptimizerTests extends ESTestCase {
assertEquals(DataType.DOUBLE, ee.dataType()); assertEquals(DataType.DOUBLE, ee.dataType());
} }
public void testFunctionNamedParamsAsFunctionArgument1() {
var query = """
from test
| WHERE MULTI_MATCH("Anna Smith", first_name, last_name, {"minimum_should_match": 2.0})
""";
var plan = optimizedPlan(query);
Limit limit = as(plan, Limit.class);
Filter filter = as(limit.child(), Filter.class);
MultiMatch match = as(filter.condition(), MultiMatch.class);
MapExpression me = as(match.options(), MapExpression.class);
assertEquals(1, me.entryExpressions().size());
EntryExpression ee = as(me.entryExpressions().get(0), EntryExpression.class);
BytesRef key = as(ee.key().fold(FoldContext.small()), BytesRef.class);
assertEquals("minimum_should_match", key.utf8ToString());
assertEquals(new Literal(EMPTY, 2.0, DataType.DOUBLE), ee.value());
assertEquals(DataType.DOUBLE, ee.dataType());
}
/** /**
* TopN[[Order[emp_no{f}#11,ASC,LAST]],1000[INTEGER]] * TopN[[Order[emp_no{f}#11,ASC,LAST]],1000[INTEGER]]
* \_Join[LEFT,[language_code{r}#5],[language_code{r}#5],[language_code{f}#22]] * \_Join[LEFT,[language_code{r}#5],[language_code{r}#5],[language_code{f}#22]]

View File

@ -11,48 +11,36 @@ import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.core.util.StringUtils;
import org.elasticsearch.xpack.esql.expression.predicate.fulltext.MultiMatchQueryPredicate;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
public class MultiMatchQueryTests extends ESTestCase { public class MultiMatchQueryTests extends ESTestCase {
public void testQueryBuilding() { public void testQueryBuilding() {
MultiMatchQueryBuilder qb = getBuilder("lenient=true"); MultiMatchQueryBuilder qb = getBuilder(Map.of("lenient", true));
assertThat(qb.lenient(), equalTo(true)); assertThat(qb.lenient(), equalTo(true));
qb = getBuilder("type=best_fields"); qb = getBuilder(Map.of("type", "best_fields"));
assertThat(qb.getType(), equalTo(MultiMatchQueryBuilder.Type.BEST_FIELDS)); assertThat(qb.getType(), equalTo(MultiMatchQueryBuilder.Type.BEST_FIELDS));
Exception e = expectThrows(IllegalArgumentException.class, () -> getBuilder("pizza=yummy")); Exception e = expectThrows(IllegalArgumentException.class, () -> getBuilder(Map.of("pizza", "yummy")));
assertThat(e.getMessage(), equalTo("illegal multi_match option [pizza]")); assertThat(e.getMessage(), equalTo("illegal multi_match option [pizza]"));
e = expectThrows(ElasticsearchParseException.class, () -> getBuilder("type=aoeu")); e = expectThrows(ElasticsearchParseException.class, () -> getBuilder(Map.of("type", "aoeu")));
assertThat(e.getMessage(), equalTo("failed to parse [multi_match] query type [aoeu]. unknown type.")); assertThat(e.getMessage(), equalTo("failed to parse [multi_match] query type [aoeu]. unknown type."));
} }
private static MultiMatchQueryBuilder getBuilder(String options) { private static MultiMatchQueryBuilder getBuilder(Map<String, Object> options) {
final Source source = new Source(1, 1, StringUtils.EMPTY); final Source source = new Source(1, 1, StringUtils.EMPTY);
final MultiMatchQueryPredicate mmqp = new MultiMatchQueryPredicate(source, "foo,bar", "eggplant", options); final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", Map.of("bar", 1.0f, "foo", 1.0f), options);
final Map<String, Float> fields = new HashMap<>();
fields.put("foo", 1.0f);
fields.put("bar", 1.0f);
final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", fields, mmqp);
return (MultiMatchQueryBuilder) mmq.asBuilder(); return (MultiMatchQueryBuilder) mmq.asBuilder();
} }
public void testToString() { public void testToString() {
final Source source = new Source(1, 1, StringUtils.EMPTY); final Source source = new Source(1, 1, StringUtils.EMPTY);
final MultiMatchQueryPredicate mmqp = new MultiMatchQueryPredicate(source, "foo,bar", "eggplant", ""); final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", Map.of("bar", 1.0f, "foo", 1.0f), null);
// Use a TreeMap so we get the fields in a predictable order.
final Map<String, Float> fields = new TreeMap<>();
fields.put("foo", 1.0f);
fields.put("bar", 1.0f);
final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", fields, mmqp);
assertEquals("MultiMatchQuery@1:2[{bar=1.0, foo=1.0}:eggplant]", mmq.toString()); assertEquals("MultiMatchQuery@1:2[{bar=1.0, foo=1.0}:eggplant]", mmq.toString());
} }
} }

View File

@ -101,7 +101,7 @@ setup:
- match: {esql.functions.coalesce: $functions_coalesce} - match: {esql.functions.coalesce: $functions_coalesce}
- gt: {esql.functions.categorize: $functions_categorize} - gt: {esql.functions.categorize: $functions_categorize}
# Testing for the entire function set isn't feasbile, so we just check that we return the correct count as an approximation. # Testing for the entire function set isn't feasbile, so we just check that we return the correct count as an approximation.
- length: {esql.functions: 136} # check the "sister" test below for a likely update to the same esql.functions length check - length: {esql.functions: 137} # check the "sister" test below for a likely update to the same esql.functions length check
--- ---
"Basic ESQL usage output (telemetry) non-snapshot version": "Basic ESQL usage output (telemetry) non-snapshot version":