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.

(cherry picked from commit 4e5d5af58a140573b8ee19d57c7839db7b779e3b)
This commit is contained in:
Andrei Stefan 2019-03-21 11:42:18 +02:00 committed by Andrei Stefan
parent a4cb92a300
commit 1a5ff05870
5 changed files with 51 additions and 5 deletions

View File

@ -64,6 +64,9 @@ SELECT last_name l FROM "test_emp" WHERE NOT (emp_no < 10003 AND last_name NOT L
whereFieldOnMatchWithAndAndOr whereFieldOnMatchWithAndAndOr
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; 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;
whereFieldWithLikeAndNotLike
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;
// TODO: (NOT) RLIKE in particular and more NOT queries in general // TODO: (NOT) RLIKE in particular and more NOT queries in general
whereIsNull whereIsNull

View File

@ -31,6 +31,7 @@ import org.elasticsearch.xpack.sql.expression.function.UnresolvedFunction;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Count; import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.sql.expression.function.scalar.Cast; import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.ArithmeticOperation; import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.ArithmeticOperation;
import org.elasticsearch.xpack.sql.expression.predicate.regex.Like;
import org.elasticsearch.xpack.sql.plan.TableIdentifier; import org.elasticsearch.xpack.sql.plan.TableIdentifier;
import org.elasticsearch.xpack.sql.plan.logical.Aggregate; import org.elasticsearch.xpack.sql.plan.logical.Aggregate;
import org.elasticsearch.xpack.sql.plan.logical.EsRelation; import org.elasticsearch.xpack.sql.plan.logical.EsRelation;
@ -848,9 +849,11 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
List<Function> list = getList(seen, fName); List<Function> list = getList(seen, fName);
for (Function seenFunction : list) { for (Function seenFunction : list) {
if (seenFunction != f && f.arguments().equals(seenFunction.arguments())) { if (seenFunction != f && f.arguments().equals(seenFunction.arguments())) {
// TODO: we should move to always compare the functions directly
// Special check for COUNT: an already seen COUNT function will be returned only if its DISTINCT property // Special check for COUNT: an already seen COUNT function will be returned only if its DISTINCT property
// matches the one from the unresolved function to be checked. // matches the one from the unresolved function to be checked.
if (seenFunction instanceof Count) { // Same for LIKE: the equals function also compares the pattern of LIKE
if (seenFunction instanceof Count || seenFunction instanceof Like) {
if (seenFunction.equals(f)){ if (seenFunction.equals(f)){
return seenFunction; return seenFunction;
} }

View File

@ -6,8 +6,10 @@
package org.elasticsearch.xpack.sql.expression.predicate.regex; package org.elasticsearch.xpack.sql.expression.predicate.regex;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo; import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.Objects;
public class Like extends RegexMatch { public class Like extends RegexMatch {
@ -31,4 +33,14 @@ public class Like extends RegexMatch {
protected Like replaceChild(Expression newLeft) { protected Like replaceChild(Expression newLeft) {
return new Like(source(), newLeft, pattern); return new Like(source(), newLeft, pattern);
} }
@Override
public boolean equals(Object obj) {
return super.equals(obj) && Objects.equals(((Like) obj).pattern(), pattern());
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), pattern());
}
} }

View File

@ -72,15 +72,15 @@ public class BoolQuery extends Query {
return boolQuery; return boolQuery;
} }
boolean isAnd() { public boolean isAnd() {
return isAnd; return isAnd;
} }
Query left() { public Query left() {
return left; return left;
} }
Query right() { public Query right() {
return right; return right;
} }

View File

@ -35,6 +35,7 @@ import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation; import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation;
import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter; import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByDateHistogram; import org.elasticsearch.xpack.sql.querydsl.agg.GroupByDateHistogram;
import org.elasticsearch.xpack.sql.querydsl.query.BoolQuery;
import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery; import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery;
import org.elasticsearch.xpack.sql.querydsl.query.NotQuery; import org.elasticsearch.xpack.sql.querydsl.query.NotQuery;
import org.elasticsearch.xpack.sql.querydsl.query.Query; import org.elasticsearch.xpack.sql.querydsl.query.Query;
@ -199,6 +200,33 @@ public class QueryTranslatorTests extends ESTestCase {
assertEquals("Scalar function (LTRIM(keyword)) not allowed (yet) as arguments for LIKE", ex.getMessage()); assertEquals("Scalar function (LTRIM(keyword)) not allowed (yet) as arguments for LIKE", ex.getMessage());
} }
public void testDifferentLikeAndNotLikePatterns() {
LogicalPlan p = plan("SELECT keyword k FROM test WHERE k LIKE 'X%' AND k NOT LIKE 'Y%'");
assertTrue(p instanceof Project);
p = ((Project) p).child();
assertTrue(p instanceof Filter);
Expression condition = ((Filter) p).condition();
QueryTranslation qt = QueryTranslator.toQuery(condition, false);
assertEquals(BoolQuery.class, qt.query.getClass());
BoolQuery bq = ((BoolQuery) qt.query);
assertTrue(bq.isAnd());
assertTrue(bq.left() instanceof QueryStringQuery);
assertTrue(bq.right() instanceof NotQuery);
NotQuery nq = (NotQuery) bq.right();
assertTrue(nq.child() instanceof QueryStringQuery);
QueryStringQuery lqsq = (QueryStringQuery) bq.left();
QueryStringQuery rqsq = (QueryStringQuery) nq.child();
assertEquals("X*", lqsq.query());
assertEquals(1, lqsq.fields().size());
assertEquals("keyword", lqsq.fields().keySet().iterator().next());
assertEquals("Y*", rqsq.query());
assertEquals(1, rqsq.fields().size());
assertEquals("keyword", rqsq.fields().keySet().iterator().next());
}
public void testTranslateNotExpression_WhereClause_Painless() { public void testTranslateNotExpression_WhereClause_Painless() {
LogicalPlan p = plan("SELECT * FROM test WHERE NOT(POSITION('x', keyword) = 0)"); LogicalPlan p = plan("SELECT * FROM test WHERE NOT(POSITION('x', keyword) = 0)");
assertTrue(p instanceof Project); assertTrue(p instanceof Project);