diff --git a/docs/changelog/125062.yaml b/docs/changelog/125062.yaml new file mode 100644 index 000000000000..04dd5ea4e273 --- /dev/null +++ b/docs/changelog/125062.yaml @@ -0,0 +1,5 @@ +pr: 125062 +summary: "Add initial version (behind snapshot) of `multi_match` function #121525" +area: Search +type: feature +issues: [121525] diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/description/multi_match.md new file mode 100644 index 000000000000..6745c7412c3b --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/description/multi_match.md @@ -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. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/examples/multi_match.md new file mode 100644 index 000000000000..570a24c34530 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/multi_match.md @@ -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 | + + diff --git a/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/multi_match.md new file mode 100644 index 000000000000..0084a100565e --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/multi_match.md @@ -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 <>. + +`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 index’s 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. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/layout/multi_match.md new file mode 100644 index 000000000000..d3ab5bc0ab26 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/multi_match.md @@ -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 +::: diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/multi_match.md new file mode 100644 index 000000000000..f42b89118e34 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/multi_match.md @@ -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. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/multi_match.md b/docs/reference/query-languages/esql/_snippets/functions/types/multi_match.md new file mode 100644 index 000000000000..6b0a9d9cbb5b --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/types/multi_match.md @@ -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 | + diff --git a/docs/reference/query-languages/esql/images/functions/multi_match.svg b/docs/reference/query-languages/esql/images/functions/multi_match.svg new file mode 100644 index 000000000000..93e27774b2c6 --- /dev/null +++ b/docs/reference/query-languages/esql/images/functions/multi_match.svg @@ -0,0 +1 @@ +MULTI_MATCH(query,fields,options) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/multi_match.json b/docs/reference/query-languages/esql/kibana/definition/functions/multi_match.json new file mode 100644 index 000000000000..f78bef22f37c --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/definition/functions/multi_match.json @@ -0,0 +1,739 @@ +{ + "comment" : "This is generated by ESQL’s 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 <>.'}, {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 index’s 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 <>.\" See <> 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 +} diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/multi_match.md b/docs/reference/query-languages/esql/kibana/docs/functions/multi_match.md new file mode 100644 index 000000000000..d3fd39fbc6c0 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/docs/functions/multi_match.md @@ -0,0 +1,15 @@ + + +### 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 +``` diff --git a/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java index 94a52e57d257..cfd2fdcda853 100644 --- a/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java @@ -53,21 +53,23 @@ public final class MultiMatchQueryBuilder extends AbstractQueryBuilder fieldsBoosts; diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java index cac801e03648..b741534736fa 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java @@ -54,6 +54,7 @@ public abstract class LuceneQueryEvaluator implements private final List perShardState; protected LuceneQueryEvaluator(BlockFactory blockFactory, ShardConfig[] shards) { + assert shards != null && shards.length > 0 : "LuceneQueryEvaluator requires shard information"; this.blockFactory = blockFactory; this.shards = shards; this.perShardState = new ArrayList<>(Collections.nCopies(shards.length, null)); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvAssert.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvAssert.java index fb8e5686d591..3f8478fe713a 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvAssert.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvAssert.java @@ -296,6 +296,7 @@ public final class CsvAssert { width[c] = header(headers.get(c), types.get(c)).length(); } 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++) { 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()); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/multi-match-function.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/multi-match-function.csv-spec new file mode 100644 index 000000000000..a821cfca9358 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/multi-match-function.csv-spec @@ -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) +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/scoring.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/scoring.csv-spec index b62a920137e6..f6f3352b4e90 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/scoring.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/scoring.csv-spec @@ -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 ; +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 required_capability: metadata_score required_capability: match_operator_colon diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/ScoringIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/ScoringIT.java index 4a76c7de8120..a9dada8e2fe3 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/ScoringIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/ScoringIT.java @@ -46,6 +46,7 @@ public class ScoringIT extends AbstractEsqlIntegTestCase { public static List params() { List params = new ArrayList<>(); 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[] { "qstr(\"content: fox\")" }); params.add(new Object[] { "kql(\"content*: fox\")" }); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 553f4474dd73..8695186cad70 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -904,6 +904,11 @@ public class EsqlCapabilities { */ 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? */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index cda2d5593b74..82e9d6a31389 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.util.FeatureFlag; import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException; 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.tree.Source; 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.fulltext.Kql; 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.Term; import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; @@ -428,6 +430,7 @@ public class EsqlFunctionRegistry { new FunctionDefinition[] { def(Kql.class, uni(Kql::new), "kql"), def(Match.class, tri(Match::new), "match"), + def(MultiMatch.class, MultiMatch::new, "multi_match"), def(QueryString.class, bi(QueryString::new), "qstr") } }; } @@ -1025,6 +1028,32 @@ public class EsqlFunctionRegistry { T build(Source source, Expression exp, List variadic); } + protected interface BinaryVariadicWithOptionsBuilder { + T build(Source source, Expression exp, List variadic, Expression options); + }; + + protected static FunctionDefinition def( + Class function, + BinaryVariadicWithOptionsBuilder 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. */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java index 6e60bb2496e1..994179be9f84 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.compute.lucene.LuceneQueryEvaluator.ShardConfig; 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.TranslationAware; 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.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.TypeResolutions; import org.elasticsearch.xpack.esql.core.expression.function.Function; import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.tree.Source; 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.expression.predicate.logical.BinaryLogic; 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.Locale; +import java.util.Map; import java.util.Objects; import java.util.function.BiConsumer; 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.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.isString; @@ -200,6 +209,14 @@ public abstract class FullTextFunction extends Function m -> "[" + m.functionName() + "] " + m.functionType(), failures ); + checkCommandsBeforeExpression( + plan, + condition, + MultiMatch.class, + lp -> (lp instanceof Limit == false) && (lp instanceof Aggregate == false), + m -> "[" + m.functionName() + "] " + m.functionType(), + failures + ); checkCommandsBeforeExpression( plan, condition, @@ -315,4 +332,39 @@ public abstract class FullTextFunction extends Function } return new LuceneQueryScoreEvaluator.Factory(shardConfigs); } + + protected static void populateOptionsMap( + final MapExpression options, + final Map optionsMap, + final TypeResolutions.ParamOrdinal paramOrdinal, + final String sourceText, + final Map 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())); + } + } + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java index 5c0a3857d778..7ef632c66d44 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextWritables.java @@ -9,7 +9,6 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; -import org.elasticsearch.xpack.esql.expression.predicate.fulltext.MultiMatchQueryPredicate; import java.util.ArrayList; import java.util.Collections; @@ -20,9 +19,9 @@ public class FullTextWritables { public static List getNamedWriteables() { List entries = new ArrayList<>(); - entries.add(MultiMatchQueryPredicate.ENTRY); entries.add(QueryString.ENTRY); entries.add(Match.ENTRY); + entries.add(MultiMatch.ENTRY); entries.add(Kql.ENTRY); if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java index 0c1d9ba56eba..bd5efb7279a9 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Match.java @@ -18,17 +18,14 @@ import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAwa 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.EntryExpression; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; 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.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.type.DataTypeConverter; import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; import org.elasticsearch.xpack.esql.core.util.Check; import org.elasticsearch.xpack.esql.core.util.NumericUtils; @@ -55,7 +52,6 @@ import java.util.Set; import java.util.function.BiConsumer; 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.MatchQueryBuilder.ANALYZER_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.SECOND; 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.isNotNull; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable; @@ -374,7 +369,6 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna } private Map matchQueryOptions() throws InvalidArgumentException { - if (options() == null) { 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 matchOptions.put(LENIENT_FIELD.getPreferredName(), true); - for (EntryExpression entry : ((MapExpression) options()).entryExpressions()) { - 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()) - ); - } - } - + populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS); return matchOptions; } @@ -492,16 +460,21 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna protected Query translate(TranslatorHandler handler) { var fieldAttribute = fieldAsFieldAttribute(); 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(); 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 fieldName = multiTypeEsField.getName(); } - // 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()); + return fieldName; } - private FieldAttribute fieldAsFieldAttribute() { + public static FieldAttribute fieldAsFieldAttribute(Expression field) { Expression fieldExpression = 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) { @@ -510,6 +483,10 @@ public class Match extends FullTextFunction implements OptionalArgument, PostAna return fieldExpression instanceof FieldAttribute fieldAttribute ? fieldAttribute : null; } + private FieldAttribute fieldAsFieldAttribute() { + return fieldAsFieldAttribute(field); + } + @Override 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 diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatch.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatch.java new file mode 100644 index 000000000000..5014263ba755 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatch.java @@ -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 QUERY_DATA_TYPES = Set.of( + KEYWORD, + BOOLEAN, + DATETIME, + DATE_NANOS, + DOUBLE, + INTEGER, + IP, + LONG, + UNSIGNED_LONG, + VERSION + ); + + public static final Set FIELD_DATA_TYPES = Set.of( + KEYWORD, + TEXT, + BOOLEAN, + DATETIME, + DATE_NANOS, + DOUBLE, + INTEGER, + IP, + LONG, + UNSIGNED_LONG, + VERSION + ); + + public static final Map 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 <> 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 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 index’s 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 <>." + ), }, + description = "(Optional) Additional options for MultiMatch, " + + "passed as <>.\"\n" + + " See <> 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 fields; + private final transient List fieldsOriginal; + private final transient Expression options; + private final transient Expression optionsOriginal; + + private static List initChildren(Expression query, List fields, Expression options) { + Stream 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 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 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 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 info() { + // Specifically create new instance with original arguments. + return NodeInfo.create(this, MultiMatch::new, query(), fieldsOriginal, optionsOriginal); + } + + @Override + protected Query translate(TranslatorHandler handler) { + Map 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 fields() { + return fields; + } + + public Expression options() { + return options; + } + + private Map getOptions() throws InvalidArgumentException { + Map 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 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() + ) + ); + } + } + }); + }; + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryString.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryString.java index 64ef10976c68..79cd7118ae3c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryString.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryString.java @@ -7,23 +7,19 @@ package org.elasticsearch.xpack.esql.expression.function.fulltext; -import org.apache.lucene.util.BytesRef; import org.elasticsearch.TransportVersions; 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.QueryBuilder; 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.Literal; 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.QueryStringQuery; 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.type.DataTypeConverter; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -42,7 +38,6 @@ import java.util.Objects; import java.util.Set; 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.ANALYZER_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.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.isFoldable; 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; @@ -330,33 +324,7 @@ public class QueryString extends FullTextFunction implements OptionalArgument { } Map matchOptions = new HashMap<>(); - for (EntryExpression entry : ((MapExpression) options()).entryExpressions()) { - 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()) - ); - } - } - + populateOptionsMap((MapExpression) options(), matchOptions, SECOND, sourceText(), ALLOWED_OPTIONS); return matchOptions; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQueryPredicate.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQueryPredicate.java deleted file mode 100644 index eb6c04be5821..000000000000 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQueryPredicate.java +++ /dev/null @@ -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 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 info() { - return NodeInfo.create(this, MultiMatchQueryPredicate::new, fieldString, query(), options()); - } - - @Override - public Expression replaceChildren(List newChildren) { - throw new UnsupportedOperationException("this type of node doesn't have any children to replace"); - } - - public String fieldString() { - return fieldString; - } - - public Map 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); - } -} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQuery.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQuery.java index 33042bd5efe4..5526283dfe5a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQuery.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQuery.java @@ -7,64 +7,74 @@ package org.elasticsearch.xpack.esql.querydsl.query; import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.core.Booleans; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.xpack.esql.core.querydsl.query.Query; import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.expression.predicate.fulltext.MultiMatchQueryPredicate; import java.util.Map; import java.util.Objects; +import java.util.TreeMap; import java.util.function.BiConsumer; 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 { - private static final Map> BUILDER_APPLIERS; + private static final Map> BUILDER_APPLIERS; 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( - 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... // appliers.put("zero_terms_query", (qb, s) -> qb.zeroTermsQuery(s)); - entry("analyzer", MultiMatchQueryBuilder::analyzer), - entry("auto_generate_synonyms_phrase_query", (qb, s) -> qb.autoGenerateSynonymsPhraseQuery(Booleans.parseBoolean(s))), - entry("fuzziness", (qb, s) -> qb.fuzziness(Fuzziness.fromString(s))), - entry("fuzzy_rewrite", MultiMatchQueryBuilder::fuzzyRewrite), - entry("fuzzy_transpositions", (qb, s) -> qb.fuzzyTranspositions(Booleans.parseBoolean(s))), - entry("lenient", (qb, s) -> qb.lenient(Booleans.parseBoolean(s))), - entry("max_expansions", (qb, s) -> qb.maxExpansions(Integer.valueOf(s))), - entry("minimum_should_match", MultiMatchQueryBuilder::minimumShouldMatch), - entry("operator", (qb, s) -> qb.operator(Operator.fromString(s))), - entry("prefix_length", (qb, s) -> qb.prefixLength(Integer.valueOf(s))), - entry("tie_breaker", (qb, s) -> qb.tieBreaker(Float.valueOf(s))), - entry("type", MultiMatchQueryBuilder::type) + entry(ANALYZER_FIELD.getPreferredName(), (qb, obj) -> qb.analyzer((String) obj)), + entry(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), (qb, obj) -> qb.autoGenerateSynonymsPhraseQuery((Boolean) obj)), + entry(FUZZINESS_FIELD.getPreferredName(), (qb, obj) -> qb.fuzziness(Fuzziness.fromString((String) obj))), + entry(FUZZY_REWRITE_FIELD.getPreferredName(), (qb, obj) -> qb.fuzzyRewrite((String) obj)), + entry(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), (qb, obj) -> qb.fuzzyTranspositions((Boolean) obj)), + entry(LENIENT_FIELD.getPreferredName(), (qb, obj) -> qb.lenient((Boolean) obj)), + entry(MAX_EXPANSIONS_FIELD.getPreferredName(), (qb, obj) -> qb.maxExpansions((Integer) obj)), + entry(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), (qb, obj) -> qb.minimumShouldMatch((String) obj)), + entry(OPERATOR_FIELD.getPreferredName(), (qb, obj) -> qb.operator(Operator.fromString((String) obj))), + entry(PREFIX_LENGTH_FIELD.getPreferredName(), (qb, obj) -> qb.prefixLength((Integer) obj)), + entry(TIE_BREAKER_FIELD.getPreferredName(), (qb, obj) -> qb.tieBreaker((Float) obj)), + entry(TYPE_FIELD.getPreferredName(), (qb, obj) -> qb.type((String) obj)) ); } private final String query; private final Map fields; - private final Map options; - private final MultiMatchQueryPredicate predicate; + private final Map options; - public MultiMatchQuery(Source source, String query, Map fields, MultiMatchQueryPredicate predicate) { + public MultiMatchQuery(Source source, String query, Map fields, Map options) { super(source); this.query = query; this.fields = fields; - this.predicate = predicate; - this.options = predicate.optionMap(); + this.options = options; } @Override protected QueryBuilder asBuilder() { final MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(query); queryBuilder.fields(fields); - queryBuilder.analyzer(predicate.analyzer()); options.forEach((k, v) -> { if (BUILDER_APPLIERS.containsKey(k)) { BUILDER_APPLIERS.get(k).accept(queryBuilder, v); @@ -77,7 +87,7 @@ public class MultiMatchQuery extends Query { @Override public int hashCode() { - return Objects.hash(query, fields, predicate); + return Objects.hash(query, fields, options); } @Override @@ -91,11 +101,17 @@ public class MultiMatchQuery extends Query { } 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 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; } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java index 189ec7774e46..ec650de27637 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java @@ -307,6 +307,10 @@ public class CsvTests extends ESTestCase { "CSV tests cannot currently handle scoring that depends on Lucene", 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()) { assertThat( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 5474be7efe0b..dab59a17805b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -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.fulltext.Match; 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.scalar.string.Concat; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring; @@ -2851,6 +2852,22 @@ public class AnalyzerTests extends ESTestCase { 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() { assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index c1416d9f83b5..e75cd5ea05a6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -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.UnsupportedEsField; 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.index.EsIndex; 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.KEYWORD; 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.VERSION; import static org.hamcrest.Matchers.containsString; @@ -1465,6 +1467,7 @@ public class VerifierTests extends ESTestCase { public void testFullTextFunctionsDisjunctions() { checkWithFullTextFunctionsDisjunctions("match(last_name, \"Smith\")"); + checkWithFullTextFunctionsDisjunctions("multi_match(\"Smith\", first_name, last_name)"); checkWithFullTextFunctionsDisjunctions("last_name : \"Smith\""); checkWithFullTextFunctionsDisjunctions("qstr(\"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 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() { assumeTrue("requires snapshot builds", Build.current().isSnapshot()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatchTests.java new file mode 100644 index 000000000000..dcf8d94834ef --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MultiMatchTests.java @@ -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) { + super(testCaseSupplier); + } + + @Override + protected Expression build(Source source, List 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; + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/AbstractFulltextSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/AbstractFulltextSerializationTests.java deleted file mode 100644 index abd46f4b2b1a..000000000000 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/AbstractFulltextSerializationTests.java +++ /dev/null @@ -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 extends AbstractExpressionSerializationTests { - - static final String OPTION_DELIMITER = ";"; - - String randomOptionOrNull() { - if (randomBoolean()) { - return null; - } - HashMap 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 randomIndividualOption() { - return Map.entry(randomAlphaOfLength(randomIntBetween(1, 4)), randomAlphaOfLength(randomIntBetween(1, 4))); - } -} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQuerySerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQuerySerializationTests.java deleted file mode 100644 index 17843e24a866..000000000000 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/fulltext/MultiMatchQuerySerializationTests.java +++ /dev/null @@ -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 { - - @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 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 randomIndividualField() { - return Map.entry(randomAlphaOfLength(randomIntBetween(1, 4)), randomFloat()); - } -} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java index b4e8df78d296..ddbecf52a3f1 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java @@ -20,6 +20,7 @@ import org.elasticsearch.index.mapper.MapperServiceTestCase; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; @@ -1657,6 +1658,37 @@ public class LocalPhysicalPlanOptimizerTests extends MapperServiceTestCase { 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 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 * LimitExec[1000[INTEGER]] diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index 725312198198..879187c382f1 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -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.Values; 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.Categorize; import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble; @@ -7378,6 +7379,24 @@ public class LogicalPlanOptimizerTests extends ESTestCase { 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]] * \_Join[LEFT,[language_code{r}#5],[language_code{r}#5],[language_code{f}#22]] diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQueryTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQueryTests.java index 93c285f5e3ab..81308a753879 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQueryTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/querydsl/query/MultiMatchQueryTests.java @@ -11,48 +11,36 @@ import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.core.tree.Source; 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.TreeMap; import static org.hamcrest.Matchers.equalTo; public class MultiMatchQueryTests extends ESTestCase { public void testQueryBuilding() { - MultiMatchQueryBuilder qb = getBuilder("lenient=true"); + MultiMatchQueryBuilder qb = getBuilder(Map.of("lenient", 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)); - 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]")); - 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.")); } - private static MultiMatchQueryBuilder getBuilder(String options) { + private static MultiMatchQueryBuilder getBuilder(Map options) { final Source source = new Source(1, 1, StringUtils.EMPTY); - final MultiMatchQueryPredicate mmqp = new MultiMatchQueryPredicate(source, "foo,bar", "eggplant", options); - final Map fields = new HashMap<>(); - fields.put("foo", 1.0f); - fields.put("bar", 1.0f); - final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", fields, mmqp); + final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", Map.of("bar", 1.0f, "foo", 1.0f), options); return (MultiMatchQueryBuilder) mmq.asBuilder(); } public void testToString() { final Source source = new Source(1, 1, StringUtils.EMPTY); - final MultiMatchQueryPredicate mmqp = new MultiMatchQueryPredicate(source, "foo,bar", "eggplant", ""); - // Use a TreeMap so we get the fields in a predictable order. - final Map fields = new TreeMap<>(); - fields.put("foo", 1.0f); - fields.put("bar", 1.0f); - final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", fields, mmqp); + final MultiMatchQuery mmq = new MultiMatchQuery(source, "eggplant", Map.of("bar", 1.0f, "foo", 1.0f), null); assertEquals("MultiMatchQuery@1:2[{bar=1.0, foo=1.0}:eggplant]", mmq.toString()); } } diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml index 25bae65958f2..838ec22a2d8b 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml @@ -101,7 +101,7 @@ setup: - match: {esql.functions.coalesce: $functions_coalesce} - 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. - - 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":