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:
parent
f8d3d685e5
commit
dfe4c6c568
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 \"*\""));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue