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
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
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.scalar.Cast;
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.logical.Aggregate;
import org.elasticsearch.xpack.sql.plan.logical.EsRelation;
@ -848,9 +849,11 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
List<Function> list = getList(seen, fName);
for (Function seenFunction : list) {
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
// 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)){
return seenFunction;
}

View File

@ -6,8 +6,10 @@
package org.elasticsearch.xpack.sql.expression.predicate.regex;
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.Source;
import java.util.Objects;
public class Like extends RegexMatch {
@ -31,4 +33,14 @@ public class Like extends RegexMatch {
protected Like replaceChild(Expression newLeft) {
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;
}
boolean isAnd() {
public boolean isAnd() {
return isAnd;
}
Query left() {
public Query left() {
return left;
}
Query right() {
public Query 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.querydsl.agg.AggFilter;
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.NotQuery;
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());
}
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() {
LogicalPlan p = plan("SELECT * FROM test WHERE NOT(POSITION('x', keyword) = 0)");
assertTrue(p instanceof Project);