Skip to content

Commit 841eb71

Browse files
committed
SQL: Fix issue with IN not resolving to underlying keyword field (#38440)
- Add resolution to the exact keyword field (if exists) for text fields. - Add proper verification and error message if underlying keyword doesn'texist. - Move check for field attribute in the comparison list to the `resolveType()` method of `IN`. Fixes: #38424
1 parent 3529bba commit 841eb71

File tree

4 files changed

+57
-28
lines changed

4 files changed

+57
-28
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/In.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*/
66
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
77

8+
import org.elasticsearch.xpack.sql.analysis.index.MappingException;
89
import org.elasticsearch.xpack.sql.expression.Expression;
910
import org.elasticsearch.xpack.sql.expression.Expressions;
11+
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
1012
import org.elasticsearch.xpack.sql.expression.Foldables;
1113
import org.elasticsearch.xpack.sql.expression.Nullability;
1214
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
@@ -25,6 +27,7 @@
2527
import java.util.StringJoiner;
2628
import java.util.stream.Collectors;
2729

30+
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
2831
import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
2932

3033
public class In extends ScalarFunction {
@@ -113,6 +116,27 @@ protected Pipe makePipe() {
113116
return new InPipe(location(), this, children().stream().map(Expressions::pipe).collect(Collectors.toList()));
114117
}
115118

119+
@Override
120+
protected TypeResolution resolveType() {
121+
if (value instanceof FieldAttribute) {
122+
try {
123+
((FieldAttribute) value).exactAttribute();
124+
} catch (MappingException e) {
125+
return new TypeResolution(format(null, "[{}] cannot operate on field of data type [{}]: {}",
126+
functionName(), value().dataType().esType, e.getMessage()));
127+
}
128+
}
129+
130+
for (Expression ex : list) {
131+
if (ex.foldable() == false) {
132+
return new TypeResolution(format(null, "Comparisons against variables are not (currently) supported; offender [{}] in [{}]",
133+
Expressions.name(ex),
134+
name()));
135+
}
136+
}
137+
return super.resolveType();
138+
}
139+
116140
@Override
117141
public int hashCode() {
118142
return Objects.hash(value, list);

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
import java.util.List;
101101
import java.util.Map;
102102
import java.util.Map.Entry;
103-
import java.util.Optional;
104103
import java.util.function.Supplier;
105104

106105
import static java.util.Collections.singletonList;
@@ -682,16 +681,6 @@ static class InComparisons extends ExpressionTranslator<In> {
682681

683682
@Override
684683
protected QueryTranslation asQuery(In in, boolean onAggs) {
685-
Optional<Expression> firstNotFoldable = in.list().stream().filter(expression -> !expression.foldable()).findFirst();
686-
687-
if (firstNotFoldable.isPresent()) {
688-
throw new SqlIllegalArgumentException(
689-
"Line {}:{}: Comparisons against variables are not (currently) supported; offender [{}] in [{}]",
690-
firstNotFoldable.get().location().getLineNumber(),
691-
firstNotFoldable.get().location().getColumnNumber(),
692-
Expressions.name(firstNotFoldable.get()),
693-
in.name());
694-
}
695684

696685
if (in.value() instanceof NamedExpression) {
697686
NamedExpression ne = (NamedExpression) in.value();
@@ -709,7 +698,9 @@ protected QueryTranslation asQuery(In in, boolean onAggs) {
709698
else {
710699
Query q = null;
711700
if (in.value() instanceof FieldAttribute) {
712-
q = new TermsQuery(in.location(), ne.name(), in.list());
701+
FieldAttribute fa = (FieldAttribute) in.value();
702+
// equality should always be against an exact match (which is important for strings)
703+
q = new TermsQuery(in.location(), fa.isInexact() ? fa.exactAttribute().name() : fa.name(), in.list());
713704
} else {
714705
q = new ScriptQuery(in.location(), in.asScript());
715706
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -351,23 +351,34 @@ public void testInNestedWithDifferentDataTypesFromLeftValue_SelectClause() {
351351
}
352352

353353
public void testInWithDifferentDataTypes_WhereClause() {
354-
assertEquals("1:49: expected data type [TEXT], value provided is of type [INTEGER]",
355-
error("SELECT * FROM test WHERE text IN ('foo', 'bar', 4)"));
354+
assertEquals("1:52: expected data type [KEYWORD], value provided is of type [INTEGER]",
355+
error("SELECT * FROM test WHERE keyword IN ('foo', 'bar', 4)"));
356356
}
357357

358358
public void testInNestedWithDifferentDataTypes_WhereClause() {
359-
assertEquals("1:60: expected data type [TEXT], value provided is of type [INTEGER]",
360-
error("SELECT * FROM test WHERE int = 1 OR text IN ('foo', 'bar', 2)"));
359+
assertEquals("1:63: expected data type [KEYWORD], value provided is of type [INTEGER]",
360+
error("SELECT * FROM test WHERE int = 1 OR keyword IN ('foo', 'bar', 2)"));
361361
}
362362

363363
public void testInWithDifferentDataTypesFromLeftValue_WhereClause() {
364-
assertEquals("1:35: expected data type [TEXT], value provided is of type [INTEGER]",
365-
error("SELECT * FROM test WHERE text IN (1, 2)"));
364+
assertEquals("1:38: expected data type [KEYWORD], value provided is of type [INTEGER]",
365+
error("SELECT * FROM test WHERE keyword IN (1, 2)"));
366366
}
367367

368368
public void testInNestedWithDifferentDataTypesFromLeftValue_WhereClause() {
369-
assertEquals("1:46: expected data type [TEXT], value provided is of type [INTEGER]",
370-
error("SELECT * FROM test WHERE int = 1 OR text IN (1, 2)"));
369+
assertEquals("1:49: expected data type [KEYWORD], value provided is of type [INTEGER]",
370+
error("SELECT * FROM test WHERE int = 1 OR keyword IN (1, 2)"));
371+
}
372+
373+
public void testInWithFieldInListOfValues() {
374+
assertEquals("1:30: Comparisons against variables are not (currently) supported; offender [int] in [int IN (1, int)]",
375+
error("SELECT * FROM test WHERE int IN (1, int)"));
376+
}
377+
378+
public void testInOnFieldTextWithNoKeyword() {
379+
assertEquals("1:31: [IN] cannot operate on field of data type [text]: " +
380+
"No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
381+
error("SELECT * FROM test WHERE text IN ('foo', 'bar')"));
371382
}
372383

373384
public void testNotSupportedAggregateOnDate() {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,8 @@ public void testTranslateInExpression_WhereClause() {
307307
tq.asBuilder().toString().replaceAll("\\s", ""));
308308
}
309309

310-
public void testTranslateInExpression_WhereClauseAndNullHandling() {
311-
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))");
310+
public void testTranslateInExpression_WhereClause_TextFieldWithKeyword() {
311+
LogicalPlan p = plan("SELECT * FROM test WHERE some.string IN ('foo', 'bar', 'lala', 'foo', concat('la', 'la'))");
312312
assertTrue(p instanceof Project);
313313
assertTrue(p.children().get(0) instanceof Filter);
314314
Expression condition = ((Filter) p.children().get(0)).condition();
@@ -317,19 +317,22 @@ public void testTranslateInExpression_WhereClauseAndNullHandling() {
317317
Query query = translation.query;
318318
assertTrue(query instanceof TermsQuery);
319319
TermsQuery tq = (TermsQuery) query;
320-
assertEquals("{\"terms\":{\"keyword\":[\"foo\",\"lala\"],\"boost\":1.0}}",
320+
assertEquals("{\"terms\":{\"some.string.typical\":[\"foo\",\"bar\",\"lala\"],\"boost\":1.0}}",
321321
tq.asBuilder().toString().replaceAll("\\s", ""));
322322
}
323323

324-
public void testTranslateInExpressionInvalidValues_WhereClause() {
325-
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', 'bar', keyword)");
324+
public void testTranslateInExpression_WhereClauseAndNullHandling() {
325+
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))");
326326
assertTrue(p instanceof Project);
327327
assertTrue(p.children().get(0) instanceof Filter);
328328
Expression condition = ((Filter) p.children().get(0)).condition();
329329
assertFalse(condition.foldable());
330-
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
331-
assertEquals("Line 1:52: Comparisons against variables are not (currently) supported; " +
332-
"offender [keyword] in [keyword IN (foo, bar, keyword)]", ex.getMessage());
330+
QueryTranslation translation = QueryTranslator.toQuery(condition, false);
331+
Query query = translation.query;
332+
assertTrue(query instanceof TermsQuery);
333+
TermsQuery tq = (TermsQuery) query;
334+
assertEquals("{\"terms\":{\"keyword\":[\"foo\",\"lala\"],\"boost\":1.0}}",
335+
tq.asBuilder().toString().replaceAll("\\s", ""));
333336
}
334337

335338
public void testTranslateInExpression_WhereClause_Painless() {

0 commit comments

Comments
 (0)