diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/In.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/In.java index f8f0bb35b504e..f76523eaf0cd9 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/In.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/In.java @@ -5,8 +5,10 @@ */ package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison; +import org.elasticsearch.xpack.sql.analysis.index.MappingException; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expressions; +import org.elasticsearch.xpack.sql.expression.FieldAttribute; import org.elasticsearch.xpack.sql.expression.Foldables; import org.elasticsearch.xpack.sql.expression.Nullability; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; @@ -105,6 +107,27 @@ protected Pipe makePipe() { return new InPipe(source(), this, children().stream().map(Expressions::pipe).collect(Collectors.toList())); } + @Override + protected TypeResolution resolveType() { + if (value instanceof FieldAttribute) { + try { + ((FieldAttribute) value).exactAttribute(); + } catch (MappingException e) { + return new TypeResolution(format(null, "[{}] cannot operate on field of data type [{}]: {}", + functionName(), value().dataType().esType, e.getMessage())); + } + } + + for (Expression ex : list) { + if (ex.foldable() == false) { + return new TypeResolution(format(null, "Comparisons against variables are not (currently) supported; offender [{}] in [{}]", + Expressions.name(ex), + name())); + } + } + return super.resolveType(); + } + @Override public int hashCode() { return Objects.hash(value, list); diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java index de529b2e4ca61..73e9ff57f379e 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java @@ -105,7 +105,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.function.Supplier; import static java.util.Collections.singletonList; @@ -708,16 +707,6 @@ static class InComparisons extends ExpressionTranslator { @Override protected QueryTranslation asQuery(In in, boolean onAggs) { - Optional firstNotFoldable = in.list().stream().filter(expression -> !expression.foldable()).findFirst(); - - if (firstNotFoldable.isPresent()) { - throw new SqlIllegalArgumentException( - "Line {}:{}: Comparisons against variables are not (currently) supported; offender [{}] in [{}]", - firstNotFoldable.get().sourceLocation().getLineNumber(), - firstNotFoldable.get().sourceLocation().getColumnNumber(), - Expressions.name(firstNotFoldable.get()), - in.name()); - } if (in.value() instanceof NamedExpression) { NamedExpression ne = (NamedExpression) in.value(); @@ -735,7 +724,9 @@ protected QueryTranslation asQuery(In in, boolean onAggs) { else { Query q = null; if (in.value() instanceof FieldAttribute) { - q = new TermsQuery(in.source(), ne.name(), in.list()); + FieldAttribute fa = (FieldAttribute) in.value(); + // equality should always be against an exact match (which is important for strings) + q = new TermsQuery(in.source(), fa.isInexact() ? fa.exactAttribute().name() : fa.name(), in.list()); } else { q = new ScriptQuery(in.source(), in.asScript()); } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java index 415472bfe3521..558d92351b069 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java @@ -385,23 +385,34 @@ public void testInNestedWithDifferentDataTypesFromLeftValue_SelectClause() { } public void testInWithDifferentDataTypes_WhereClause() { - assertEquals("1:49: expected data type [text], value provided is of type [integer]", - error("SELECT * FROM test WHERE text IN ('foo', 'bar', 4)")); + assertEquals("1:52: expected data type [keyword], value provided is of type [integer]", + error("SELECT * FROM test WHERE keyword IN ('foo', 'bar', 4)")); } public void testInNestedWithDifferentDataTypes_WhereClause() { - assertEquals("1:60: expected data type [text], value provided is of type [integer]", - error("SELECT * FROM test WHERE int = 1 OR text IN ('foo', 'bar', 2)")); + assertEquals("1:63: expected data type [keyword], value provided is of type [integer]", + error("SELECT * FROM test WHERE int = 1 OR keyword IN ('foo', 'bar', 2)")); } public void testInWithDifferentDataTypesFromLeftValue_WhereClause() { - assertEquals("1:35: expected data type [text], value provided is of type [integer]", - error("SELECT * FROM test WHERE text IN (1, 2)")); + assertEquals("1:38: expected data type [keyword], value provided is of type [integer]", + error("SELECT * FROM test WHERE keyword IN (1, 2)")); } public void testInNestedWithDifferentDataTypesFromLeftValue_WhereClause() { - assertEquals("1:46: expected data type [text], value provided is of type [integer]", - error("SELECT * FROM test WHERE int = 1 OR text IN (1, 2)")); + assertEquals("1:49: expected data type [keyword], value provided is of type [integer]", + error("SELECT * FROM test WHERE int = 1 OR keyword IN (1, 2)")); + } + + public void testInWithFieldInListOfValues() { + assertEquals("1:26: Comparisons against variables are not (currently) supported; offender [int] in [int IN (1, int)]", + error("SELECT * FROM test WHERE int IN (1, int)")); + } + + public void testInOnFieldTextWithNoKeyword() { + assertEquals("1:26: [IN] cannot operate on field of data type [text]: " + + "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead", + error("SELECT * FROM test WHERE text IN ('foo', 'bar')")); } public void testNotSupportedAggregateOnDate() { diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java index ef7cdf54b89ab..2d94e7660e122 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java @@ -309,8 +309,8 @@ public void testTranslateInExpression_WhereClause() { tq.asBuilder().toString().replaceAll("\\s", "")); } - public void testTranslateInExpression_WhereClauseAndNullHandling() { - LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))"); + public void testTranslateInExpression_WhereClause_TextFieldWithKeyword() { + LogicalPlan p = plan("SELECT * FROM test WHERE some.string IN ('foo', 'bar', 'lala', 'foo', concat('la', 'la'))"); assertTrue(p instanceof Project); assertTrue(p.children().get(0) instanceof Filter); Expression condition = ((Filter) p.children().get(0)).condition(); @@ -319,21 +319,22 @@ public void testTranslateInExpression_WhereClauseAndNullHandling() { Query query = translation.query; assertTrue(query instanceof TermsQuery); TermsQuery tq = (TermsQuery) query; - assertEquals("{\"terms\":{\"keyword\":[\"foo\",\"lala\"],\"boost\":1.0}}", + assertEquals("{\"terms\":{\"some.string.typical\":[\"foo\",\"bar\",\"lala\"],\"boost\":1.0}}", tq.asBuilder().toString().replaceAll("\\s", "")); } - public void testTranslateInExpressionInvalidValues_WhereClause() { - LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', 'bar', keyword)"); + public void testTranslateInExpression_WhereClauseAndNullHandling() { + LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))"); assertTrue(p instanceof Project); assertTrue(p.children().get(0) instanceof Filter); Expression condition = ((Filter) p.children().get(0)).condition(); assertFalse(condition.foldable()); - SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false)); - assertEquals( - "Line 1:52: Comparisons against variables are not (currently) supported; " - + "offender [keyword] in [keyword IN ('foo', 'bar', keyword)]", - ex.getMessage()); + QueryTranslation translation = QueryTranslator.toQuery(condition, false); + Query query = translation.query; + assertTrue(query instanceof TermsQuery); + TermsQuery tq = (TermsQuery) query; + assertEquals("{\"terms\":{\"keyword\":[\"foo\",\"lala\"],\"boost\":1.0}}", + tq.asBuilder().toString().replaceAll("\\s", "")); } public void testTranslateInExpression_WhereClause_Painless() {