Skip to content

Commit 2049651

Browse files
costindavidkyle
authored andcommitted
SQL: Fix translation of LIKE/RLIKE keywords (#36672)
* SQL: Fix translation of LIKE/RLIKE keywords Refactor Like/RLike functions to simplify internals and improve query translation when chained or within a script context. Fix #36039 Fix #36584
1 parent b112743 commit 2049651

File tree

14 files changed

+158
-154
lines changed

14 files changed

+158
-154
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ aggMaxWithAlias
280280
SELECT gender g, MAX(emp_no) m FROM "test_emp" GROUP BY g ORDER BY gender;
281281
aggMaxOnDate
282282
SELECT gender, MAX(birth_date) m FROM "test_emp" GROUP BY gender ORDER BY gender;
283+
aggAvgAndMaxWithLikeFilter
284+
SELECT CAST(AVG(salary) AS LONG) AS avg, CAST(SUM(salary) AS LONG) AS s FROM "test_emp" WHERE first_name LIKE 'G%';
283285

284286
// Conditional MAX
285287
aggMaxWithHaving

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ SELECT DAY_OF_WEEK(birth_date) day, COUNT(*) c FROM test_emp WHERE DAY_OF_WEEK(b
119119
currentTimestampYear
120120
SELECT YEAR(CURRENT_TIMESTAMP()) AS result;
121121

122-
currentTimestampMonth
122+
//
123+
// H2 uses the local timezone instead of the specified one
124+
//
125+
currentTimestampMonth-Ignore
123126
SELECT MONTH(CURRENT_TIMESTAMP()) AS result;
124127

125128
currentTimestampHour-Ignore

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ whereFieldWithNotEqualsOnString
4949
SELECT last_name l FROM "test_emp" WHERE emp_no < 10003 AND gender <> 'M';
5050
whereFieldWithLikeMatch
5151
SELECT last_name l FROM "test_emp" WHERE emp_no < 10003 AND last_name LIKE 'K%';
52+
whereFieldWithNotLikeMatch
53+
SELECT last_name l FROM "test_emp" WHERE emp_no < 10020 AND first_name NOT LIKE 'Ma%';
5254

5355
whereFieldWithOrderNot
5456
SELECT last_name l FROM "test_emp" WHERE NOT emp_no < 10003 ORDER BY emp_no LIMIT 5;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ public static Object nullif(Object left, Object right) {
165165
// Regex
166166
//
167167
public static Boolean regex(String value, String pattern) {
168+
// TODO: this needs to be improved to avoid creating the pattern on every call
168169
return RegexOperation.match(value, pattern);
169170
}
170171

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,24 @@
1111

1212
public class Like extends RegexMatch {
1313

14-
public Like(Location location, Expression left, LikePattern right) {
15-
super(location, left, right);
16-
}
14+
private final LikePattern pattern;
1715

18-
@Override
19-
protected NodeInfo<Like> info() {
20-
return NodeInfo.create(this, Like::new, left(), pattern());
16+
public Like(Location location, Expression left, LikePattern pattern) {
17+
super(location, left, pattern.asJavaRegex());
18+
this.pattern = pattern;
2119
}
2220

2321
public LikePattern pattern() {
24-
return (LikePattern) right();
22+
return pattern;
2523
}
2624

2725
@Override
28-
protected Like replaceChildren(Expression newLeft, Expression newRight) {
29-
return new Like(location(), newLeft, (LikePattern) newRight);
26+
protected NodeInfo<Like> info() {
27+
return NodeInfo.create(this, Like::new, field(), pattern);
3028
}
3129

3230
@Override
33-
protected String asString(Expression pattern) {
34-
return ((LikePattern) pattern).asJavaRegex();
31+
protected Like replaceChild(Expression newLeft) {
32+
return new Like(location(), newLeft, pattern);
3533
}
3634
}

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

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
*/
66
package org.elasticsearch.xpack.sql.expression.predicate.regex;
77

8-
import org.elasticsearch.xpack.sql.expression.LeafExpression;
9-
import org.elasticsearch.xpack.sql.tree.Location;
10-
import org.elasticsearch.xpack.sql.tree.NodeInfo;
11-
import org.elasticsearch.xpack.sql.type.DataType;
128
import org.elasticsearch.xpack.sql.util.StringUtils;
139

1410
import java.util.Objects;
@@ -21,7 +17,7 @@
2117
*
2218
* To prevent conflicts with ES, the string and char must be validated to not contain '*'.
2319
*/
24-
public class LikePattern extends LeafExpression {
20+
public class LikePattern {
2521

2622
private final String pattern;
2723
private final char escape;
@@ -30,8 +26,7 @@ public class LikePattern extends LeafExpression {
3026
private final String wildcard;
3127
private final String indexNameWildcard;
3228

33-
public LikePattern(Location location, String pattern, char escape) {
34-
super(location);
29+
public LikePattern(String pattern, char escape) {
3530
this.pattern = pattern;
3631
this.escape = escape;
3732
// early initialization to force string validation
@@ -40,11 +35,6 @@ public LikePattern(Location location, String pattern, char escape) {
4035
this.indexNameWildcard = StringUtils.likeToIndexWildcard(pattern, escape);
4136
}
4237

43-
@Override
44-
protected NodeInfo<LikePattern> info() {
45-
return NodeInfo.create(this, LikePattern::new, pattern, escape);
46-
}
47-
4838
public String pattern() {
4939
return pattern;
5040
}
@@ -74,16 +64,6 @@ public String asIndexNameWildcard() {
7464
return indexNameWildcard;
7565
}
7666

77-
@Override
78-
public boolean nullable() {
79-
return false;
80-
}
81-
82-
@Override
83-
public DataType dataType() {
84-
return DataType.KEYWORD;
85-
}
86-
8767
@Override
8868
public int hashCode() {
8969
return Objects.hash(pattern, escape);

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,29 @@
66
package org.elasticsearch.xpack.sql.expression.predicate.regex;
77

88
import org.elasticsearch.xpack.sql.expression.Expression;
9-
import org.elasticsearch.xpack.sql.expression.Literal;
109
import org.elasticsearch.xpack.sql.tree.Location;
1110
import org.elasticsearch.xpack.sql.tree.NodeInfo;
1211

1312
public class RLike extends RegexMatch {
1413

15-
public RLike(Location location, Expression left, Literal right) {
16-
super(location, left, right);
14+
private final String pattern;
15+
16+
public RLike(Location location, Expression left, String pattern) {
17+
super(location, left, pattern);
18+
this.pattern = pattern;
1719
}
1820

19-
@Override
20-
protected NodeInfo<RLike> info() {
21-
return NodeInfo.create(this, RLike::new, left(), (Literal) right());
21+
public String pattern() {
22+
return pattern;
2223
}
2324

2425
@Override
25-
protected RLike replaceChildren(Expression newLeft, Expression newRight) {
26-
return new RLike(location(), newLeft, (Literal) newRight);
26+
protected NodeInfo<RLike> info() {
27+
return NodeInfo.create(this, RLike::new, field(), pattern);
2728
}
2829

2930
@Override
30-
protected String asString(Expression pattern) {
31-
return pattern.fold().toString();
31+
protected RLike replaceChild(Expression newChild) {
32+
return new RLike(location(), newChild, pattern);
3233
}
3334
}

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,45 @@
77
package org.elasticsearch.xpack.sql.expression.predicate.regex;
88

99
import org.elasticsearch.xpack.sql.expression.Expression;
10-
import org.elasticsearch.xpack.sql.expression.predicate.BinaryPredicate;
10+
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
11+
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
1112
import org.elasticsearch.xpack.sql.expression.predicate.regex.RegexProcessor.RegexOperation;
1213
import org.elasticsearch.xpack.sql.tree.Location;
1314
import org.elasticsearch.xpack.sql.type.DataType;
1415

15-
public abstract class RegexMatch extends BinaryPredicate<String, String, Boolean, RegexOperation> {
16+
public abstract class RegexMatch extends UnaryScalarFunction {
1617

17-
protected RegexMatch(Location location, Expression value, Expression pattern) {
18-
super(location, value, pattern, RegexOperation.INSTANCE);
18+
private final String pattern;
19+
20+
protected RegexMatch(Location location, Expression value, String pattern) {
21+
super(location, value);
22+
this.pattern = pattern;
1923
}
2024

2125
@Override
2226
public DataType dataType() {
2327
return DataType.BOOLEAN;
2428
}
2529

30+
@Override
31+
public boolean nullable() {
32+
return field().nullable() && pattern != null;
33+
}
34+
2635
@Override
2736
public boolean foldable() {
2837
// right() is not directly foldable in any context but Like can fold it.
29-
return left().foldable();
38+
return field().foldable();
3039
}
3140

3241
@Override
3342
public Boolean fold() {
34-
Object val = left().fold();
35-
val = val != null ? val.toString() : val;
36-
return function().apply((String) val, asString(right()));
43+
Object val = field().fold();
44+
return RegexOperation.match(val, pattern);
3745
}
3846

39-
protected abstract String asString(Expression pattern);
47+
@Override
48+
protected Processor makeProcessor() {
49+
return new RegexProcessor(pattern);
50+
}
4051
}

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

Lines changed: 0 additions & 34 deletions
This file was deleted.

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

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,79 +7,71 @@
77

88
import org.elasticsearch.common.io.stream.StreamInput;
99
import org.elasticsearch.common.io.stream.StreamOutput;
10-
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
11-
import org.elasticsearch.xpack.sql.expression.gen.processor.BinaryProcessor;
1210
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
13-
import org.elasticsearch.xpack.sql.expression.predicate.PredicateBiFunction;
1411

1512
import java.io.IOException;
1613
import java.util.Objects;
1714
import java.util.regex.Pattern;
1815

19-
public class RegexProcessor extends BinaryProcessor {
16+
public class RegexProcessor implements Processor {
2017

21-
public static class RegexOperation implements PredicateBiFunction<String, String, Boolean> {
18+
public static class RegexOperation {
2219

23-
public static final RegexOperation INSTANCE = new RegexOperation();
20+
public static Boolean match(Object value, Pattern pattern) {
21+
if (pattern == null) {
22+
return Boolean.TRUE;
23+
}
2424

25-
@Override
26-
public String name() {
27-
return symbol();
28-
}
25+
if (value == null) {
26+
return null;
27+
}
2928

30-
@Override
31-
public String symbol() {
32-
return "REGEX";
29+
return pattern.matcher(value.toString()).matches();
3330
}
3431

35-
@Override
36-
public Boolean doApply(String value, String pattern) {
37-
return match(value, pattern);
38-
}
32+
public static Boolean match(Object value, String pattern) {
33+
if (pattern == null) {
34+
return Boolean.TRUE;
35+
}
3936

40-
public static Boolean match(Object value, Object pattern) {
41-
if (value == null || pattern == null) {
37+
if (value == null) {
4238
return null;
4339
}
4440

45-
Pattern p = Pattern.compile(pattern.toString());
46-
return p.matcher(value.toString()).matches();
41+
return Pattern.compile(pattern).matcher(value.toString()).matches();
4742
}
4843
}
4944

5045
public static final String NAME = "rgx";
5146

52-
public RegexProcessor(Processor value, Processor pattern) {
53-
super(value, pattern);
54-
}
47+
private Pattern pattern;
5548

56-
public RegexProcessor(StreamInput in) throws IOException {
57-
super(in);
49+
public RegexProcessor(String pattern) {
50+
this.pattern = pattern != null ? Pattern.compile(pattern) : null;
5851
}
5952

6053
@Override
61-
protected Boolean doProcess(Object value, Object pattern) {
62-
return RegexOperation.match(value, pattern);
54+
public String getWriteableName() {
55+
return NAME;
6356
}
6457

65-
@Override
66-
protected void checkParameter(Object param) {
67-
if (!(param instanceof String || param instanceof Character)) {
68-
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", param);
69-
}
58+
public RegexProcessor(StreamInput in) throws IOException {
59+
this(in.readOptionalString());
7060
}
7161

7262
@Override
73-
public String getWriteableName() {
74-
return NAME;
63+
public void writeTo(StreamOutput out) throws IOException {
64+
out.writeOptionalString(pattern != null ? pattern.toString() : null);
7565
}
7666

7767
@Override
78-
protected void doWrite(StreamOutput out) throws IOException {}
68+
public Object process(Object input) {
69+
return RegexOperation.match(input, pattern);
70+
}
7971

8072
@Override
8173
public int hashCode() {
82-
return Objects.hash(left(), right());
74+
return Objects.hash(pattern);
8375
}
8476

8577
@Override
@@ -93,6 +85,6 @@ public boolean equals(Object obj) {
9385
}
9486

9587
RegexProcessor other = (RegexProcessor) obj;
96-
return Objects.equals(left(), other.left()) && Objects.equals(right(), other.right());
88+
return Objects.equals(pattern, other.pattern);
9789
}
9890
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public Expression visitPredicated(PredicatedContext ctx) {
232232
e = new Like(loc, exp, visitPattern(pCtx.pattern()));
233233
break;
234234
case SqlBaseParser.RLIKE:
235-
e = new RLike(loc, exp, new Literal(source(pCtx.regex), string(pCtx.regex), DataType.KEYWORD));
235+
e = new RLike(loc, exp, string(pCtx.regex));
236236
break;
237237
case SqlBaseParser.NULL:
238238
// shortcut to avoid double negation later on (since there's no IsNull (missing in ES is a negated exists))
@@ -301,7 +301,7 @@ public LikePattern visitPattern(PatternContext ctx) {
301301
}
302302
}
303303

304-
return new LikePattern(source(ctx), pattern, escape);
304+
return new LikePattern(pattern, escape);
305305
}
306306

307307

0 commit comments

Comments
 (0)