diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/term.md b/docs/reference/query-languages/esql/_snippets/functions/description/term.md deleted file mode 100644 index b09624e632597..0000000000000 --- a/docs/reference/query-languages/esql/_snippets/functions/description/term.md +++ /dev/null @@ -1,6 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -**Description** - -Performs a Term query on the specified field. Returns true if the provided term matches the row. - diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/term.md b/docs/reference/query-languages/esql/_snippets/functions/examples/term.md deleted file mode 100644 index e502061ae60c5..0000000000000 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/term.md +++ /dev/null @@ -1,16 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -**Example** - -```esql -FROM books -| WHERE TERM(author, "gabriel") -``` - -| book_no:keyword | title:text | -| --- | --- | -| 4814 | El Coronel No Tiene Quien Le Escriba / No One Writes to the Colonel (Spanish Edition) | -| 4917 | Autumn of the Patriarch | -| 6380 | La hojarasca (Spanish Edition) | - - diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/term.md b/docs/reference/query-languages/esql/_snippets/functions/layout/term.md deleted file mode 100644 index 5c386f0c03e6f..0000000000000 --- a/docs/reference/query-languages/esql/_snippets/functions/layout/term.md +++ /dev/null @@ -1,27 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -## `TERM` [esql-term] -```{applies_to} -stack: preview -serverless: preview -``` - -**Syntax** - -:::{image} ../../../images/functions/term.svg -:alt: Embedded -:class: text-center -::: - - -:::{include} ../parameters/term.md -::: - -:::{include} ../description/term.md -::: - -:::{include} ../types/term.md -::: - -:::{include} ../examples/term.md -::: diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/term.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/term.md deleted file mode 100644 index c11ce5a3b44f8..0000000000000 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/term.md +++ /dev/null @@ -1,10 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -**Parameters** - -`field` -: Field that the query will target. - -`query` -: Term you wish to find in the provided field. - diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/term.md b/docs/reference/query-languages/esql/_snippets/functions/types/term.md deleted file mode 100644 index bd920d4d116b7..0000000000000 --- a/docs/reference/query-languages/esql/_snippets/functions/types/term.md +++ /dev/null @@ -1,11 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -**Supported types** - -| field | query | result | -| --- | --- | --- | -| keyword | keyword | boolean | -| keyword | text | boolean | -| text | keyword | boolean | -| text | text | boolean | - diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/term.json b/docs/reference/query-languages/esql/kibana/definition/functions/term.json deleted file mode 100644 index e13c3ace836a2..0000000000000 --- a/docs/reference/query-languages/esql/kibana/definition/functions/term.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", - "type" : "scalar", - "name" : "term", - "description" : "Performs a Term query on the specified field. Returns true if the provided term matches the row.", - "signatures" : [ - { - "params" : [ - { - "name" : "field", - "type" : "keyword", - "optional" : false, - "description" : "Field that the query will target." - }, - { - "name" : "query", - "type" : "keyword", - "optional" : false, - "description" : "Term you wish to find in the provided field." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "keyword", - "optional" : false, - "description" : "Field that the query will target." - }, - { - "name" : "query", - "type" : "text", - "optional" : false, - "description" : "Term you wish to find in the provided field." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "text", - "optional" : false, - "description" : "Field that the query will target." - }, - { - "name" : "query", - "type" : "keyword", - "optional" : false, - "description" : "Term you wish to find in the provided field." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, - { - "params" : [ - { - "name" : "field", - "type" : "text", - "optional" : false, - "description" : "Field that the query will target." - }, - { - "name" : "query", - "type" : "text", - "optional" : false, - "description" : "Term you wish to find in the provided field." - } - ], - "variadic" : false, - "returnType" : "boolean" - } - ], - "examples" : [ - "FROM books\n| WHERE TERM(author, \"gabriel\")" - ], - "preview" : true, - "snapshot_only" : true -} diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/term.md b/docs/reference/query-languages/esql/kibana/docs/functions/term.md deleted file mode 100644 index 0b9a90759f031..0000000000000 --- a/docs/reference/query-languages/esql/kibana/docs/functions/term.md +++ /dev/null @@ -1,9 +0,0 @@ -% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. - -### TERM -Performs a Term query on the specified field. Returns true if the provided term matches the row. - -```esql -FROM books -| WHERE TERM(author, "gabriel") -``` diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join-expression.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join-expression.csv-spec index 90b6ded78be2e..9d1134444bd8c 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join-expression.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join-expression.csv-spec @@ -954,26 +954,6 @@ id_left:integer | name_left:keyword | other1_from_first_join:keyword | other1:ke 1 | Alice | beta | alpha ; -lookupJoinExpressionWithTerm -required_capability: join_lookup_v12 -required_capability: lookup_join_with_full_text_function -required_capability: term_function - -FROM multi_column_joinable -| RENAME id_int AS id_left, is_active_bool AS is_active_left -| LOOKUP JOIN multi_column_joinable_lookup ON TERM(other1, "beta") AND id_int == id_left and is_active_left == is_active_bool -| WHERE other2 IS NOT NULL -| KEEP id_left, name_str, extra1, other1, other2 -| SORT id_left, name_str, extra1, other1, other2 -; - -warning:Line 3:3: evaluation of [LOOKUP JOIN multi_column_joinable_lookup ON TERM(other1, \"beta\") AND id_int == id_left and is_active_left == is_active_bool] failed, treating result as null. Only first 20 failures recorded. -warning:Line 3:3: java.lang.IllegalArgumentException: LOOKUP JOIN encountered multi-value - -id_left:integer | name_str:keyword | extra1:keyword | other1:keyword | other2:integer -1 | Alice | foo | beta | 2000 -; - lookupJoinExpressionWithQueryString required_capability: join_lookup_v12 required_capability: lookup_join_with_full_text_function diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/term-function.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/term-function.csv-spec deleted file mode 100644 index 836e6f29f7e56..0000000000000 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/term-function.csv-spec +++ /dev/null @@ -1,209 +0,0 @@ -############################################### -# Tests for Term function -# - -termWithTextField -required_capability: term_function - -// tag::term-with-field[] -FROM books -| WHERE TERM(author, "gabriel") -// end::term-with-field[] -| KEEP book_no, title -| LIMIT 3 -; -ignoreOrder:true - -// tag::term-with-field-result[] -book_no:keyword | title:text -4814 | El Coronel No Tiene Quien Le Escriba / No One Writes to the Colonel (Spanish Edition) -4917 | Autumn of the Patriarch -6380 | La hojarasca (Spanish Edition) -// end::term-with-field-result[] -; - -termWithKeywordField -required_capability: term_function - -from employees -| where term(first_name, "Guoxiang") -| keep emp_no, first_name; - -// tag::term-with-keyword-field-result[] -emp_no:integer | first_name:keyword -10015 | Guoxiang -; -// end::term-with-keyword-field-result[] - -termWithQueryExpressions -required_capability: term_function - -from books -| where term(author, CONCAT("gab", "riel")) -| keep book_no, title; -ignoreOrder:true - -book_no:keyword | title:text -4814 | El Coronel No Tiene Quien Le Escriba / No One Writes to the Colonel (Spanish Edition) -4917 | Autumn of the Patriarch -6380 | La hojarasca (Spanish Edition) -; - -termAfterKeep -required_capability: term_function - -from books -| keep book_no, author -| where term(author, "faulkner") -| 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 -; - -termAfterDrop -required_capability: term_function - -from books -| drop ratings, description, year, publisher, title, author.keyword -| where term(author, "william") -| keep book_no, author -| sort book_no -| limit 2; - -book_no:keyword | author:text -2713 | William Faulkner -2883 | William Faulkner -; - -termAfterEval -required_capability: term_function - -from books -| eval stars = to_long(ratings / 2.0) -| where term(author, "colleen") -| sort book_no -| keep book_no, author, stars -| limit 2; - -book_no:keyword | author:text | stars:long -2847 | Colleen Faulkner | 3 -4502 | Colleen Faulkner | 3 -; - -termWithConjunction -required_capability: term_function - -from books -| where term(author, "tolkien") and ratings > 4.95 -| eval author = mv_sort(author) -| keep book_no, ratings, author; -ignoreOrder:true - -book_no:keyword | ratings:double | author:keyword -2301 | 5.0 | John Ronald Reuel Tolkien -3254 | 5.0 | [Christopher Tolkien, John Ronald Reuel Tolkien] -7350 | 5.0 | [Christopher Tolkien, John Ronald Reuel Tolkien] -; - -termWithConjunctionAndSort -required_capability: term_function - -from books -| where term(author, "tolkien") and ratings > 4.95 -| eval author = mv_sort(author) -| keep book_no, ratings, author -| sort book_no; - -book_no:keyword | ratings:double | author:keyword -2301 | 5.0 | John Ronald Reuel Tolkien -3254 | 5.0 | [Christopher Tolkien, John Ronald Reuel Tolkien] -7350 | 5.0 | [Christopher Tolkien, John Ronald Reuel Tolkien] -; - -termWithFunctionPushedToLucene -required_capability: term_function - -from hosts -| where term(host, "beta") and cidr_match(ip1, "127.0.0.2/32", "127.0.0.3/32") -| keep card, host, ip0, ip1; -ignoreOrder:true - -card:keyword |host:keyword |ip0:ip |ip1:ip -eth1 |beta |127.0.0.1 |127.0.0.2 -; - -termWithNonPushableConjunction -required_capability: term_function - -from books -| where term(title, "rings") and length(title) > 75 -| keep book_no, title; -ignoreOrder:true - -book_no:keyword | title:text -4023 | A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings -; - -termWithMultipleWhereClauses -required_capability: term_function - -from books -| where term(title, "rings") -| where term(title, "lord") -| keep book_no, title; -ignoreOrder:true - -book_no:keyword | title:text -2675 | The Lord of the Rings - Boxed Set -2714 | Return of the King Being the Third Part of The Lord of the Rings -4023 | A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings -7140 | The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1) -; - -termWithMultivaluedField -required_capability: term_function - -from employees -| where term(job_positions, "Data Scientist") -| keep emp_no, first_name, last_name -| sort emp_no asc -| limit 2; -ignoreOrder:true - -emp_no:integer | first_name:keyword | last_name:keyword -10014 | Berni | Genin -10017 | Cristinel | Bouloucos -; - -testWithMultiValuedFieldWithConjunction -required_capability: term_function - -from employees -| where term(job_positions, "Data Scientist") and term(first_name, "Cristinel") -| keep emp_no, first_name, last_name -| limit 1; - -emp_no:integer | first_name:keyword | last_name:keyword -10017 | Cristinel | Bouloucos -; - -termWithConjQueryStringFunctions -required_capability: term_function -required_capability: qstr_function - -from employees -| where term(job_positions, "Data Scientist") and qstr("first_name: Cristinel and gender: F") -| keep emp_no, first_name, last_name -| sort emp_no ASC -| limit 1; -ignoreOrder:true - -emp_no:integer | first_name:keyword | last_name:keyword -10017 | Cristinel | Bouloucos -; 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 f6d27bcb628f0..d76aeb5e56879 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 @@ -16,7 +16,6 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.action.AbstractEsqlIntegTestCase; -import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.kql.KqlPlugin; import org.junit.Before; @@ -51,9 +50,6 @@ public static List params() { params.add(new Object[] { "qstr(\"content: fox\")" }); params.add(new Object[] { "kql(\"content*: fox\")" }); params.add(new Object[] { "match_phrase(content, \"fox\")" }); - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - params.add(new Object[] { "term(content, \"fox\")" }); - } return params; } diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/TermIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/TermIT.java deleted file mode 100644 index a56dec2ff4883..0000000000000 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/TermIT.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.plugin; - -import org.elasticsearch.xpack.esql.VerificationException; -import org.elasticsearch.xpack.esql.action.AbstractEsqlIntegTestCase; -import org.elasticsearch.xpack.esql.action.EsqlCapabilities; -import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; -import org.elasticsearch.xpack.esql.action.EsqlQueryResponse; -import org.junit.Before; - -import java.util.List; - -import static org.elasticsearch.xpack.esql.plugin.QueryStringIT.createAndPopulateIndex; -import static org.hamcrest.CoreMatchers.containsString; - -public class TermIT extends AbstractEsqlIntegTestCase { - - @Before - public void setupIndex() { - createAndPopulateIndex(this::ensureYellow); - } - - @Override - public EsqlQueryResponse run(EsqlQueryRequest request) { - assumeTrue("term function capability not available", EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()); - return super.run(request); - } - - public void testSimpleTermQuery() throws Exception { - var query = """ - FROM test - | WHERE term(content,"dog") - | KEEP id - | SORT id - """; - - try (var resp = run(query)) { - assertColumnNames(resp.columns(), List.of("id")); - assertColumnTypes(resp.columns(), List.of("integer")); - assertValues(resp.values(), List.of(List.of(1), List.of(3), List.of(4), List.of(5))); - } - } - - public void testTermWithinEval() { - var query = """ - FROM test - | EVAL term_query = term(title,"fox") - """; - - var error = expectThrows(VerificationException.class, () -> run(query)); - assertThat(error.getMessage(), containsString("[Term] function is only supported in WHERE and STATS commands")); - } - - public void testMultipleTerm() { - var query = """ - FROM test - | WHERE term(content,"fox") AND term(content,"brown") - | KEEP id - | SORT id - """; - - try (var resp = run(query)) { - assertColumnNames(resp.columns(), List.of("id")); - assertColumnTypes(resp.columns(), List.of("integer")); - assertValues(resp.values(), List.of(List.of(2), List.of(4), List.of(5))); - } - } - - public void testNotWhereTerm() { - var query = """ - FROM test - | WHERE NOT term(content,"brown") - | KEEP id - | SORT id - """; - - try (var resp = run(query)) { - assertColumnNames(resp.columns(), List.of("id")); - assertColumnTypes(resp.columns(), List.of("integer")); - assertValues(resp.values(), List.of(List.of(3))); - } - } - - public void testTermWithLookupJoin() { - var query = """ - FROM test - | LOOKUP JOIN test_lookup ON id - | WHERE id > 0 AND TERM(lookup_content, "fox") - """; - - var error = expectThrows(VerificationException.class, () -> run(query)); - assertThat( - error.getMessage(), - containsString( - "line 3:25: [Term] function cannot operate on [lookup_content], supplied by an index [test_lookup] " - + "in non-STANDARD mode [lookup]" - ) - ); - } -} 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 183be6b2559a6..76c33aada90de 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 @@ -922,11 +922,6 @@ public enum Cap { */ METADATA_SCORE, - /** - * Term function - */ - TERM_FUNCTION(Build.current().isSnapshot()), - /** * Additional types for match function and operator */ 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 28b6447920fae..7f03ccfb171fb 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 @@ -67,7 +67,6 @@ 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.Score; -import org.elasticsearch.xpack.esql.expression.function.fulltext.Term; import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; import org.elasticsearch.xpack.esql.expression.function.grouping.Categorize; import org.elasticsearch.xpack.esql.expression.function.grouping.TBucket; @@ -589,7 +588,6 @@ private static FunctionDefinition[][] snapshotFunctions() { def(AllFirst.class, bi(AllFirst::new), "all_first"), def(AllLast.class, bi(AllLast::new), "all_last"), def(Last.class, bi(Last::new), "last"), - def(Term.class, bi(Term::new), "term"), // dense vector functions def(Magnitude.class, Magnitude::new, "v_magnitude"), def(ToDateRange.class, ToDateRange::new, "to_date_range", "to_daterange") } }; 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 4bd76870c9944..e42fdecc8acf0 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 @@ -8,30 +8,13 @@ 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.function.scalar.score.Decay; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class FullTextWritables { public static List getNamedWriteables() { - List entries = new ArrayList<>(); - - entries.add(QueryString.ENTRY); - entries.add(Match.ENTRY); - entries.add(MultiMatch.ENTRY); - entries.add(Kql.ENTRY); - entries.add(MatchPhrase.ENTRY); - entries.add(Score.ENTRY); - entries.add(Decay.ENTRY); - - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - entries.add(Term.ENTRY); - } - - return Collections.unmodifiableList(entries); + return List.of(QueryString.ENTRY, Match.ENTRY, MultiMatch.ENTRY, Kql.ENTRY, MatchPhrase.ENTRY, Score.ENTRY, Decay.ENTRY); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Term.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Term.java deleted file mode 100644 index 310eb98c767ad..0000000000000 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/Term.java +++ /dev/null @@ -1,157 +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.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.QueryBuilder; -import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; -import org.elasticsearch.xpack.esql.common.Failures; -import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; -import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; -import org.elasticsearch.xpack.esql.core.querydsl.query.Query; -import org.elasticsearch.xpack.esql.core.querydsl.query.TermQuery; -import org.elasticsearch.xpack.esql.core.tree.NodeInfo; -import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.expression.Foldables; -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.Param; -import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; -import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates; -import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; -import org.elasticsearch.xpack.esql.planner.TranslatorHandler; - -import java.io.IOException; -import java.util.List; -import java.util.function.BiConsumer; - -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.isNotNull; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString; - -/** - * Full text function that performs a {@link TermQuery} . - */ -public class Term extends FullTextFunction implements PostAnalysisPlanVerificationAware { - - public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Term", Term::readFrom); - - private final Expression field; - - @FunctionInfo( - returnType = "boolean", - preview = true, - appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW) }, - description = "Performs a Term query on the specified field. Returns true if the provided term matches the row.", - examples = { @Example(file = "term-function", tag = "term-with-field") } - ) - public Term( - Source source, - @Param(name = "field", type = { "keyword", "text" }, description = "Field that the query will target.") Expression field, - @Param( - name = "query", - type = { "keyword", "text" }, - description = "Term you wish to find in the provided field." - ) Expression termQuery - ) { - this(source, field, termQuery, null); - } - - public Term(Source source, Expression field, Expression termQuery, QueryBuilder queryBuilder) { - super(source, termQuery, List.of(field, termQuery), queryBuilder); - this.field = field; - } - - private static Term readFrom(StreamInput in) throws IOException { - Source source = Source.readFrom((PlanStreamInput) in); - Expression field = in.readNamedWriteable(Expression.class); - Expression query = in.readNamedWriteable(Expression.class); - QueryBuilder queryBuilder = in.readOptionalNamedWriteable(QueryBuilder.class); - return new Term(source, field, query, queryBuilder); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - source().writeTo(out); - out.writeNamedWriteable(field()); - out.writeNamedWriteable(query()); - out.writeOptionalNamedWriteable(queryBuilder()); - } - - @Override - public String getWriteableName() { - return ENTRY.name; - } - - @Override - protected TypeResolution resolveParams() { - return resolveField().and(resolveQuery(SECOND)); - } - - private TypeResolution resolveField() { - return isNotNull(field, sourceText(), FIRST).and(isString(field, sourceText(), FIRST)); - } - - @Override - public BiConsumer postAnalysisPlanVerification() { - return (plan, failures) -> { - super.postAnalysisPlanVerification().accept(plan, failures); - fieldVerifier(plan, this, field, failures); - }; - } - - @Override - public BiConsumer postOptimizationPlanVerification() { - // check plan again after predicates are pushed down into subqueries - return (plan, failures) -> { - super.postOptimizationPlanVerification().accept(plan, failures); - fieldVerifier(plan, this, field, failures); - }; - } - - @Override - public Expression replaceChildren(List newChildren) { - return new Term(source(), newChildren.get(0), newChildren.get(1), queryBuilder()); - } - - @Override - protected NodeInfo info() { - return NodeInfo.create(this, Term::new, field, query(), queryBuilder()); - } - - protected TypeResolutions.ParamOrdinal queryParamOrdinal() { - return SECOND; - } - - @Override - protected Query translate(LucenePushdownPredicates pushdownPredicates, TranslatorHandler handler) { - // Uses a term query that contributes to scoring - return new TermQuery(source(), ((FieldAttribute) field()).name(), Foldables.queryAsObject(query(), sourceText()), false, true); - } - - @Override - public Expression replaceQueryBuilder(QueryBuilder queryBuilder) { - return new Term(source(), field, query(), queryBuilder); - } - - public Expression field() { - return field; - } - - // TODO: method can be dropped, to allow failure messages contain the capitalized function name, aligned with similar functions/classes - @Override - public String functionName() { - return ENTRY.name; - } -} 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 bcb609dfbbdd4..7e4aa0cbab5ff 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 @@ -326,10 +326,6 @@ public final void test() throws Throwable { "lookup join disabled for csv tests", testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName()) ); - assumeFalse( - "can't use TERM function in csv tests", - testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.TERM_FUNCTION.capabilityName()) - ); assumeFalse( "CSV tests cannot correctly handle the field caps change", testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.SEMANTIC_TEXT_FIELD_CAPS.capabilityName()) 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 5e41bc49fa8f8..daf37c2449ddd 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 @@ -1411,10 +1411,6 @@ public void testFieldBasedFullTextFunctions() throws Exception { checkFieldBasedWithNonIndexedColumn("MultiMatch", "multi_match(\"cat\", text)", "function"); checkFieldBasedFunctionNotAllowedAfterCommands("MultiMatch", "function", "multi_match(\"Meditation\", title)"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - checkFieldBasedWithNonIndexedColumn("Term", "term(text, \"cat\")", "function"); - checkFieldBasedFunctionNotAllowedAfterCommands("Term", "function", "term(title, \"Meditation\")"); - } checkFieldBasedFunctionNotAllowedAfterCommands("KNN", "function", "knn(vector, [1, 2, 3])"); } @@ -1541,9 +1537,6 @@ public void testFullTextFunctionsOnlyAllowedInWhere() throws Exception { checkFullTextFunctionsOnlyAllowedInWhere("KQL", "kql(\"Meditation\")", "function"); checkFullTextFunctionsOnlyAllowedInWhere("MatchPhrase", "match_phrase(title, \"Meditation\")", "function"); checkFullTextFunctionsOnlyAllowedInWhere("KNN", "knn(vector, [0, 1, 2])", "function"); - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - checkFullTextFunctionsOnlyAllowedInWhere("Term", "term(title, \"Meditation\")", "function"); - } if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkFullTextFunctionsOnlyAllowedInWhere("MultiMatch", "multi_match(\"Meditation\", title, body)", "function"); } @@ -1594,9 +1587,6 @@ public void testFullTextFunctionsDisjunctions() { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkWithFullTextFunctionsDisjunctions("multi_match(\"Meditation\", title, body)"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - checkWithFullTextFunctionsDisjunctions("term(title, \"Meditation\")"); - } } private void checkWithFullTextFunctionsDisjunctions(String functionInvocation) { @@ -1657,9 +1647,6 @@ public void testFullTextFunctionsWithNonBooleanFunctions() { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkFullTextFunctionsWithNonBooleanFunctions("MultiMatch", "multi_match(\"Meditation\", title, body)", "function"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - checkFullTextFunctionsWithNonBooleanFunctions("Term", "term(title, \"Meditation\")", "function"); - } } private void checkFullTextFunctionsWithNonBooleanFunctions(String functionName, String functionInvocation, String functionType) { @@ -1726,9 +1713,6 @@ public void testFullTextFunctionsTargetsExistingField() throws Exception { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { testFullTextFunctionTargetsExistingField("multi_match(\"Meditation\", title)"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - testFullTextFunctionTargetsExistingField("term(fist_name, \"Meditation\")"); - } } private void testFullTextFunctionTargetsExistingField(String functionInvocation) throws Exception { @@ -2576,9 +2560,6 @@ public void testFullTextFunctionCurrentlyUnsupportedBehaviour() throws Exception if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { testFullTextFunctionsCurrentlyUnsupportedBehaviour("multi_match(\"Meditation\", title)"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - testFullTextFunctionsCurrentlyUnsupportedBehaviour("term(title, \"Meditation\")"); - } } private void testFullTextFunctionsCurrentlyUnsupportedBehaviour(String functionInvocation) throws Exception { @@ -2601,10 +2582,6 @@ public void testFullTextFunctionsNullArgs() throws Exception { checkFullTextFunctionNullArgs("multi_match(null, title)", "first"); checkFullTextFunctionAcceptsNullField("multi_match(\"test\", null)"); } - if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { - checkFullTextFunctionNullArgs("term(null, \"query\")", "first"); - checkFullTextFunctionNullArgs("term(title, null)", "second"); - } } private void checkFullTextFunctionNullArgs(String functionInvocation, String argOrdinal) throws Exception { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java deleted file mode 100644 index a00858e8e1e43..0000000000000 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java +++ /dev/null @@ -1,44 +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.function.fulltext; - -import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; -import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; -import org.hamcrest.Matcher; - -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import static org.hamcrest.Matchers.equalTo; - -public class TermErrorTests extends ErrorsForCasesWithoutExamplesTestCase { - @Override - protected List cases() { - return paramsToSuppliers(TermTests.parameters()); - } - - @Override - protected Stream> testCandidates(List cases, Set> valid) { - // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests - return super.testCandidates(cases, valid).filter(sig -> false == sig.contains(DataType.NULL)); - } - - @Override - protected Expression build(Source source, List args) { - return new Term(source, args.get(0), args.get(1)); - } - - @Override - protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { - return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "string")); - } -} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java deleted file mode 100644 index eca246e20452c..0000000000000 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java +++ /dev/null @@ -1,127 +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.function.fulltext; - -import com.carrotsearch.randomizedtesting.annotations.Name; -import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; - -import org.apache.lucene.util.BytesRef; -import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression; -import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; -import org.elasticsearch.xpack.esql.expression.function.FunctionName; -import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; - -import static org.hamcrest.Matchers.equalTo; - -@FunctionName("term") -public class TermTests extends AbstractFunctionTestCase { - - public TermTests(@Name("TestCase") Supplier testCaseSupplier) { - this.testCase = testCaseSupplier.get(); - } - - @ParametersFactory - public static Iterable parameters() { - List> supportedPerPosition = supportedParams(); - List suppliers = new LinkedList<>(); - for (DataType fieldType : DataType.stringTypes()) { - for (DataType queryType : DataType.stringTypes()) { - addPositiveTestCase(List.of(fieldType, queryType), suppliers); - addNonFieldTestCase(List.of(fieldType, queryType), supportedPerPosition, suppliers); - } - } - - return parameterSuppliersFromTypedData(suppliers); - } - - protected static List> supportedParams() { - Set supportedTextParams = Set.of(DataType.KEYWORD, DataType.TEXT); - Set supportedNumericParams = Set.of(DataType.DOUBLE, DataType.INTEGER); - Set supportedFuzzinessParams = Set.of(DataType.INTEGER, DataType.KEYWORD, DataType.TEXT); - List> supportedPerPosition = List.of( - supportedTextParams, - supportedTextParams, - supportedNumericParams, - supportedFuzzinessParams - ); - return supportedPerPosition; - } - - protected static void addPositiveTestCase(List paramDataTypes, List suppliers) { - - // Positive case - creates an ES field from the field parameter type - suppliers.add( - new TestCaseSupplier( - getTestCaseName(paramDataTypes, "-ES field"), - paramDataTypes, - () -> new TestCaseSupplier.TestCase( - getTestParams(paramDataTypes), - "EndsWithEvaluator[str=Attribute[channel=0], suffix=Attribute[channel=1]]", - DataType.BOOLEAN, - equalTo(true) - ) - ) - ); - } - - private static void addNonFieldTestCase( - List paramDataTypes, - List> supportedPerPosition, - List suppliers - ) { - // Negative case - use directly the field parameter type - suppliers.add( - new TestCaseSupplier( - getTestCaseName(paramDataTypes, "-non ES field"), - paramDataTypes, - typeErrorSupplier(true, supportedPerPosition, paramDataTypes, TermTests::matchTypeErrorSupplier) - ) - ); - } - - private static List getTestParams(List paramDataTypes) { - String fieldName = randomIdentifier(); - List params = new ArrayList<>(); - params.add( - new TestCaseSupplier.TypedData( - new FieldExpression(fieldName, List.of(new FieldExpression.FieldValue(fieldName))), - paramDataTypes.get(0), - "field" - ) - ); - params.add(new TestCaseSupplier.TypedData(new BytesRef(randomAlphaOfLength(10)), paramDataTypes.get(1), "query")); - return params; - } - - private static String getTestCaseName(List paramDataTypes, String fieldType) { - StringBuilder sb = new StringBuilder(); - sb.append("<"); - sb.append(paramDataTypes.get(0)).append(fieldType).append(", "); - sb.append(paramDataTypes.get(1)); - sb.append(">"); - return sb.toString(); - } - - private static String matchTypeErrorSupplier(boolean includeOrdinal, List> validPerPosition, List types) { - return "[] cannot operate on [" + types.getFirst().typeName() + "], which is not a field from an index mapping"; - } - - @Override - protected Expression build(Source source, List args) { - return new Match(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null); - } -} 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 9e7b372edbf8a..1e0497486e840 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 @@ -27,7 +27,6 @@ import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.EsqlTestUtils.TestSearchStats; import org.elasticsearch.xpack.esql.VerificationException; -import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.analysis.Analyzer; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; @@ -1339,35 +1338,6 @@ public void testKnnKOverridesLimitK() { assertEquals(expectedQuery.toString(), planStr.get()); } - /** - * Expecting - * LimitExec[1000[INTEGER]] - * \_ExchangeExec[[_meta_field{f}#8, emp_no{f}#2, first_name{f}#3, gender{f}#4, job{f}#9, job.raw{f}#10, languages{f}#5, last_na - * me{f}#6, long_noidx{f}#11, salary{f}#7],false] - * \_ProjectExec[[_meta_field{f}#8, emp_no{f}#2, first_name{f}#3, gender{f}#4, job{f}#9, job.raw{f}#10, languages{f}#5, last_na - * me{f}#6, long_noidx{f}#11, salary{f}#7]] - * \_FieldExtractExec[_meta_field{f}#8, emp_no{f}#2, first_name{f}#3, gen] - * \_EsQueryExec[test], indexMode[standard], query[{"term":{"last_name":{"query":"Smith"}}}] - */ - public void testTermFunction() { - // Skip test if the term function is not enabled. - assumeTrue("term function capability not available", EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()); - - var plan = plannerOptimizer.plan(""" - from test - | where term(last_name, "Smith") - """, IS_SV_STATS); - - var limit = as(plan, LimitExec.class); - var exchange = as(limit.child(), ExchangeExec.class); - var project = as(exchange.child(), ProjectExec.class); - var field = as(project.child(), FieldExtractExec.class); - var query = as(field.child(), EsQueryExec.class); - assertThat(as(query.limit(), Literal.class).value(), is(1000)); - var expected = termQuery("last_name", "Smith"); - assertThat(query.query().toString(), is(expected.toString())); - } - /** * Expects * LimitExec[1000[INTEGER]]