Skip to content

Commit 4e5d5af

Browse files
authored
SQL: fix LIKE function equality by considering its pattern as well (#40260)
* Define a equals method for Like function so that the pattern used is considered in the equality check. Whenever the functions are resolved this check should be used.
1 parent 9beb31f commit 4e5d5af

File tree

5 files changed

+51
-5
lines changed

5 files changed

+51
-5
lines changed

x-pack/plugin/sql/qa/src/main/resources/filter.sql-spec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ SELECT last_name l FROM "test_emp" WHERE NOT (emp_no < 10003 AND last_name NOT L
6464
whereFieldOnMatchWithAndAndOr
6565
SELECT last_name l FROM "test_emp" WHERE emp_no < 10003 AND (gender = 'M' AND NOT FALSE OR last_name LIKE 'K%') ORDER BY emp_no;
6666

67+
whereFieldWithLikeAndNotLike
68+
SELECT COUNT(*), last_name AS f FROM test_emp WHERE last_name LIKE '%o%' AND last_name NOT LIKE '%f%' GROUP BY f HAVING COUNT(*) > 1;
69+
6770
// TODO: (NOT) RLIKE in particular and more NOT queries in general
6871

6972
whereIsNull

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
3232
import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
3333
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.ArithmeticOperation;
34+
import org.elasticsearch.xpack.sql.expression.predicate.regex.Like;
3435
import org.elasticsearch.xpack.sql.plan.TableIdentifier;
3536
import org.elasticsearch.xpack.sql.plan.logical.Aggregate;
3637
import org.elasticsearch.xpack.sql.plan.logical.EsRelation;
@@ -848,9 +849,11 @@ private Expression collectResolvedAndReplace(Expression e, Map<String, List<Func
848849
List<Function> list = getList(seen, fName);
849850
for (Function seenFunction : list) {
850851
if (seenFunction != f && f.arguments().equals(seenFunction.arguments())) {
852+
// TODO: we should move to always compare the functions directly
851853
// Special check for COUNT: an already seen COUNT function will be returned only if its DISTINCT property
852854
// matches the one from the unresolved function to be checked.
853-
if (seenFunction instanceof Count) {
855+
// Same for LIKE: the equals function also compares the pattern of LIKE
856+
if (seenFunction instanceof Count || seenFunction instanceof Like) {
854857
if (seenFunction.equals(f)){
855858
return seenFunction;
856859
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/regex/Like.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
package org.elasticsearch.xpack.sql.expression.predicate.regex;
77

88
import org.elasticsearch.xpack.sql.expression.Expression;
9-
import org.elasticsearch.xpack.sql.tree.Source;
109
import org.elasticsearch.xpack.sql.tree.NodeInfo;
10+
import org.elasticsearch.xpack.sql.tree.Source;
11+
12+
import java.util.Objects;
1113

1214
public class Like extends RegexMatch {
1315

@@ -31,4 +33,14 @@ protected NodeInfo<Like> info() {
3133
protected Like replaceChild(Expression newLeft) {
3234
return new Like(source(), newLeft, pattern);
3335
}
36+
37+
@Override
38+
public boolean equals(Object obj) {
39+
return super.equals(obj) && Objects.equals(((Like) obj).pattern(), pattern());
40+
}
41+
42+
@Override
43+
public int hashCode() {
44+
return Objects.hash(super.hashCode(), pattern());
45+
}
3446
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/query/BoolQuery.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ public QueryBuilder asBuilder() {
7272
return boolQuery;
7373
}
7474

75-
boolean isAnd() {
75+
public boolean isAnd() {
7676
return isAnd;
7777
}
7878

79-
Query left() {
79+
public Query left() {
8080
return left;
8181
}
8282

83-
Query right() {
83+
public Query right() {
8484
return right;
8585
}
8686

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation;
3636
import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter;
3737
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByDateHistogram;
38+
import org.elasticsearch.xpack.sql.querydsl.query.BoolQuery;
3839
import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery;
3940
import org.elasticsearch.xpack.sql.querydsl.query.NotQuery;
4041
import org.elasticsearch.xpack.sql.querydsl.query.Query;
@@ -198,6 +199,33 @@ public void testLikeConstructsNotSupported() {
198199
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
199200
assertEquals("Scalar function (LTRIM(keyword)) not allowed (yet) as arguments for LIKE", ex.getMessage());
200201
}
202+
203+
public void testDifferentLikeAndNotLikePatterns() {
204+
LogicalPlan p = plan("SELECT keyword k FROM test WHERE k LIKE 'X%' AND k NOT LIKE 'Y%'");
205+
assertTrue(p instanceof Project);
206+
p = ((Project) p).child();
207+
assertTrue(p instanceof Filter);
208+
209+
Expression condition = ((Filter) p).condition();
210+
QueryTranslation qt = QueryTranslator.toQuery(condition, false);
211+
assertEquals(BoolQuery.class, qt.query.getClass());
212+
BoolQuery bq = ((BoolQuery) qt.query);
213+
assertTrue(bq.isAnd());
214+
assertTrue(bq.left() instanceof QueryStringQuery);
215+
assertTrue(bq.right() instanceof NotQuery);
216+
217+
NotQuery nq = (NotQuery) bq.right();
218+
assertTrue(nq.child() instanceof QueryStringQuery);
219+
QueryStringQuery lqsq = (QueryStringQuery) bq.left();
220+
QueryStringQuery rqsq = (QueryStringQuery) nq.child();
221+
222+
assertEquals("X*", lqsq.query());
223+
assertEquals(1, lqsq.fields().size());
224+
assertEquals("keyword", lqsq.fields().keySet().iterator().next());
225+
assertEquals("Y*", rqsq.query());
226+
assertEquals(1, rqsq.fields().size());
227+
assertEquals("keyword", rqsq.fields().keySet().iterator().next());
228+
}
201229

202230
public void testTranslateNotExpression_WhereClause_Painless() {
203231
LogicalPlan p = plan("SELECT * FROM test WHERE NOT(POSITION('x', keyword) = 0)");

0 commit comments

Comments
 (0)