SQL: have LIKE/RLIKE use wildcard and regexp queries (#40628)

* Have LIKE and RLIKE only use term-level queries (wildcard and regexp respectively). They
are already working only with exact fields, thus be in-line with how
SQL works in general (what you index is what you search on).

(cherry picked from commit 1bba887d481b49db231a1442922f1813952dcc67)
This commit is contained in:
Andrei Stefan 2019-04-01 18:10:51 +03:00 committed by Andrei Stefan
parent f8d3d685e5
commit dfe4c6c568
4 changed files with 57 additions and 45 deletions

View File

@ -88,7 +88,7 @@ public class FieldAttribute extends TypedAttribute {
public FieldAttribute exactAttribute() { public FieldAttribute exactAttribute() {
EsField exactField = field.getExactField(); EsField exactField = field.getExactField();
if (exactField.equals(field) == false) { if (exactField.equals(field) == false) {
return innerField(field.getExactField()); return innerField(exactField);
} }
return this; return this;
} }

View File

@ -24,9 +24,9 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.sql.expression.function.aggregate.ExtendedStats; import org.elasticsearch.xpack.sql.expression.function.aggregate.ExtendedStats;
import org.elasticsearch.xpack.sql.expression.function.aggregate.First; import org.elasticsearch.xpack.sql.expression.function.aggregate.First;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Last; import org.elasticsearch.xpack.sql.expression.function.aggregate.Last;
import org.elasticsearch.xpack.sql.expression.function.aggregate.MedianAbsoluteDeviation;
import org.elasticsearch.xpack.sql.expression.function.aggregate.MatrixStats; import org.elasticsearch.xpack.sql.expression.function.aggregate.MatrixStats;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Max; import org.elasticsearch.xpack.sql.expression.function.aggregate.Max;
import org.elasticsearch.xpack.sql.expression.function.aggregate.MedianAbsoluteDeviation;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Min; import org.elasticsearch.xpack.sql.expression.function.aggregate.Min;
import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRanks; import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRanks;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles; import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles;
@ -470,7 +470,6 @@ final class QueryTranslator {
af.nodeString()); af.nodeString());
} }
// TODO: need to optimize on ngram
// TODO: see whether escaping is needed // TODO: see whether escaping is needed
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
static class Likes extends ExpressionTranslator<RegexMatch> { static class Likes extends ExpressionTranslator<RegexMatch> {
@ -478,34 +477,23 @@ final class QueryTranslator {
@Override @Override
protected QueryTranslation asQuery(RegexMatch e, boolean onAggs) { protected QueryTranslation asQuery(RegexMatch e, boolean onAggs) {
Query q = null; Query q = null;
boolean inexact = true; String targetFieldName = null;
String target = null;
if (e.field() instanceof FieldAttribute) { if (e.field() instanceof FieldAttribute) {
target = nameOf(((FieldAttribute) e.field()).exactAttribute()); targetFieldName = nameOf(((FieldAttribute) e.field()).exactAttribute());
} else { } else {
throw new SqlIllegalArgumentException("Scalar function ({}) not allowed (yet) as arguments for LIKE", throw new SqlIllegalArgumentException("Scalar function [{}] not allowed (yet) as argument for " + e.functionName(),
Expressions.name(e.field())); Expressions.name(e.field()));
} }
if (e instanceof Like) { if (e instanceof Like) {
LikePattern p = ((Like) e).pattern(); LikePattern p = ((Like) e).pattern();
if (inexact) { q = new WildcardQuery(e.source(), targetFieldName, p.asLuceneWildcard());
q = new QueryStringQuery(e.source(), p.asLuceneWildcard(), target);
}
else {
q = new WildcardQuery(e.source(), nameOf(e.field()), p.asLuceneWildcard());
}
} }
if (e instanceof RLike) { if (e instanceof RLike) {
String pattern = ((RLike) e).pattern(); String pattern = ((RLike) e).pattern();
if (inexact) { q = new RegexQuery(e.source(), targetFieldName, pattern);
q = new QueryStringQuery(e.source(), "/" + pattern + "/", target);
}
else {
q = new RegexQuery(e.source(), nameOf(e.field()), pattern);
}
} }
return q != null ? new QueryTranslation(wrapIfNested(q, e.field())) : null; return q != null ? new QueryTranslation(wrapIfNested(q, e.field())) : null;

View File

@ -535,12 +535,18 @@ public class VerifierErrorMessagesTests extends ESTestCase {
error("SELECT INSERT('text', 1, 2, 3)")); error("SELECT INSERT('text', 1, 2, 3)"));
} }
public void testInvalidTypeForRegexMatch() { public void testInvalidTypeForLikeMatch() {
assertEquals("1:26: [text LIKE 'foo'] cannot operate on field of data type [text]: " + assertEquals("1:26: [text LIKE 'foo'] cannot operate on field of data type [text]: " +
"No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead", "No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
error("SELECT * FROM test WHERE text LIKE 'foo'")); error("SELECT * FROM test WHERE text LIKE 'foo'"));
} }
public void testInvalidTypeForRLikeMatch() {
assertEquals("1:26: [text RLIKE 'foo'] 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 RLIKE 'foo'"));
}
public void testAllowCorrectFieldsInIncompatibleMappings() { public void testAllowCorrectFieldsInIncompatibleMappings() {
assertNotNull(incompatibleAccept("SELECT languages FROM \"*\"")); assertNotNull(incompatibleAccept("SELECT languages FROM \"*\""));
} }

View File

@ -41,11 +41,12 @@ 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;
import org.elasticsearch.xpack.sql.querydsl.query.QueryStringQuery;
import org.elasticsearch.xpack.sql.querydsl.query.RangeQuery; import org.elasticsearch.xpack.sql.querydsl.query.RangeQuery;
import org.elasticsearch.xpack.sql.querydsl.query.RegexQuery;
import org.elasticsearch.xpack.sql.querydsl.query.ScriptQuery; import org.elasticsearch.xpack.sql.querydsl.query.ScriptQuery;
import org.elasticsearch.xpack.sql.querydsl.query.TermQuery; import org.elasticsearch.xpack.sql.querydsl.query.TermQuery;
import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery; import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery;
import org.elasticsearch.xpack.sql.querydsl.query.WildcardQuery;
import org.elasticsearch.xpack.sql.stats.Metrics; import org.elasticsearch.xpack.sql.stats.Metrics;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.EsField; import org.elasticsearch.xpack.sql.type.EsField;
@ -186,20 +187,41 @@ public class QueryTranslatorTests extends ESTestCase {
assertTrue(p instanceof Filter); assertTrue(p instanceof Filter);
Expression condition = ((Filter) p).condition(); Expression condition = ((Filter) p).condition();
QueryTranslation qt = QueryTranslator.toQuery(condition, false); QueryTranslation qt = QueryTranslator.toQuery(condition, false);
assertEquals(QueryStringQuery.class, qt.query.getClass()); assertEquals(WildcardQuery.class, qt.query.getClass());
QueryStringQuery qsq = ((QueryStringQuery) qt.query); WildcardQuery qsq = ((WildcardQuery) qt.query);
assertEquals(1, qsq.fields().size()); assertEquals("some.string.typical", qsq.field());
assertEquals("some.string.typical", qsq.fields().keySet().iterator().next()); }
public void testRLikeOnInexact() {
LogicalPlan p = plan("SELECT * FROM test WHERE some.string RLIKE '.*a.*'");
assertTrue(p instanceof Project);
p = ((Project) p).child();
assertTrue(p instanceof Filter);
Expression condition = ((Filter) p).condition();
QueryTranslation qt = QueryTranslator.toQuery(condition, false);
assertEquals(RegexQuery.class, qt.query.getClass());
RegexQuery qsq = ((RegexQuery) qt.query);
assertEquals("some.string.typical", qsq.field());
} }
public void testLikeConstructsNotSupported() { public void testLikeConstructsNotSupported() {
LogicalPlan p = plan("SELECT LTRIM(keyword) lt FROM test WHERE LTRIM(keyword) LIKE '%a%'"); LogicalPlan p = plan("SELECT LTRIM(keyword) lt FROM test WHERE LTRIM(keyword) like '%a%'");
assertTrue(p instanceof Project); assertTrue(p instanceof Project);
p = ((Project) p).child(); p = ((Project) p).child();
assertTrue(p instanceof Filter); assertTrue(p instanceof Filter);
Expression condition = ((Filter) p).condition(); Expression condition = ((Filter) p).condition();
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false)); SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
assertEquals("Scalar function (LTRIM(keyword)) not allowed (yet) as arguments for LIKE", ex.getMessage()); assertEquals("Scalar function [LTRIM(keyword)] not allowed (yet) as argument for LIKE", ex.getMessage());
}
public void testRLikeConstructsNotSupported() {
LogicalPlan p = plan("SELECT LTRIM(keyword) lt FROM test WHERE LTRIM(keyword) RLIKE '.*a.*'");
assertTrue(p instanceof Project);
p = ((Project) p).child();
assertTrue(p instanceof Filter);
Expression condition = ((Filter) p).condition();
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
assertEquals("Scalar function [LTRIM(keyword)] not allowed (yet) as argument for RLIKE", ex.getMessage());
} }
public void testDifferentLikeAndNotLikePatterns() { public void testDifferentLikeAndNotLikePatterns() {
@ -213,20 +235,18 @@ public class QueryTranslatorTests extends ESTestCase {
assertEquals(BoolQuery.class, qt.query.getClass()); assertEquals(BoolQuery.class, qt.query.getClass());
BoolQuery bq = ((BoolQuery) qt.query); BoolQuery bq = ((BoolQuery) qt.query);
assertTrue(bq.isAnd()); assertTrue(bq.isAnd());
assertTrue(bq.left() instanceof QueryStringQuery); assertTrue(bq.left() instanceof WildcardQuery);
assertTrue(bq.right() instanceof NotQuery); assertTrue(bq.right() instanceof NotQuery);
NotQuery nq = (NotQuery) bq.right(); NotQuery nq = (NotQuery) bq.right();
assertTrue(nq.child() instanceof QueryStringQuery); assertTrue(nq.child() instanceof WildcardQuery);
QueryStringQuery lqsq = (QueryStringQuery) bq.left(); WildcardQuery lqsq = (WildcardQuery) bq.left();
QueryStringQuery rqsq = (QueryStringQuery) nq.child(); WildcardQuery rqsq = (WildcardQuery) nq.child();
assertEquals("X*", lqsq.query()); assertEquals("X*", lqsq.query());
assertEquals(1, lqsq.fields().size()); assertEquals("keyword", lqsq.field());
assertEquals("keyword", lqsq.fields().keySet().iterator().next());
assertEquals("Y*", rqsq.query()); assertEquals("Y*", rqsq.query());
assertEquals(1, rqsq.fields().size()); assertEquals("keyword", rqsq.field());
assertEquals("keyword", rqsq.fields().keySet().iterator().next());
} }
public void testRLikePatterns() { public void testRLikePatterns() {
@ -248,20 +268,18 @@ public class QueryTranslatorTests extends ESTestCase {
assertEquals(BoolQuery.class, qt.query.getClass()); assertEquals(BoolQuery.class, qt.query.getClass());
BoolQuery bq = ((BoolQuery) qt.query); BoolQuery bq = ((BoolQuery) qt.query);
assertTrue(bq.isAnd()); assertTrue(bq.isAnd());
assertTrue(bq.left() instanceof QueryStringQuery); assertTrue(bq.left() instanceof RegexQuery);
assertTrue(bq.right() instanceof NotQuery); assertTrue(bq.right() instanceof NotQuery);
NotQuery nq = (NotQuery) bq.right(); NotQuery nq = (NotQuery) bq.right();
assertTrue(nq.child() instanceof QueryStringQuery); assertTrue(nq.child() instanceof RegexQuery);
QueryStringQuery lqsq = (QueryStringQuery) bq.left(); RegexQuery lqsq = (RegexQuery) bq.left();
QueryStringQuery rqsq = (QueryStringQuery) nq.child(); RegexQuery rqsq = (RegexQuery) nq.child();
assertEquals("/" + firstPattern + "/", lqsq.query()); assertEquals(firstPattern, lqsq.regex());
assertEquals(1, lqsq.fields().size()); assertEquals("keyword", lqsq.field());
assertEquals("keyword", lqsq.fields().keySet().iterator().next()); assertEquals(secondPattern, rqsq.regex());
assertEquals("/" + secondPattern + "/", rqsq.query()); assertEquals("keyword", rqsq.field());
assertEquals(1, rqsq.fields().size());
assertEquals("keyword", rqsq.fields().keySet().iterator().next());
} }
public void testTranslateNotExpression_WhereClause_Painless() { public void testTranslateNotExpression_WhereClause_Painless() {