Skip to content

Commit 72e72e4

Browse files
New Func Added
1 parent 72229b0 commit 72e72e4

File tree

14 files changed

+1009
-482
lines changed

14 files changed

+1009
-482
lines changed

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/predicate/regex/WildcardPattern.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
import org.apache.lucene.util.automaton.Automaton;
1212
import org.apache.lucene.util.automaton.Operations;
1313
import org.apache.lucene.util.automaton.RegExp;
14+
import org.elasticsearch.common.io.stream.StreamInput;
15+
import org.elasticsearch.common.io.stream.StreamOutput;
1416
import org.elasticsearch.xpack.esql.core.util.StringUtils;
1517

18+
import java.io.IOException;
1619
import java.util.Objects;
1720

1821
import static org.elasticsearch.xpack.esql.core.util.StringUtils.luceneWildcardToRegExp;
@@ -34,6 +37,9 @@ public WildcardPattern(String pattern) {
3437
// early initialization to force string validation
3538
this.regex = StringUtils.wildcardToJavaPattern(pattern, '\\');
3639
}
40+
public WildcardPattern(StreamInput in) throws IOException{
41+
this(in.readString());
42+
}
3743

3844
public String pattern() {
3945
return wildcard;
@@ -87,4 +93,12 @@ public boolean equals(Object obj) {
8793
WildcardPattern other = (WildcardPattern) obj;
8894
return Objects.equals(wildcard, other.wildcard);
8995
}
96+
97+
public void writeTo(StreamOutput out) throws IOException {
98+
out.writeString(wildcard);
99+
}
100+
101+
public static WildcardPattern readFrom(StreamInput out) throws IOException {
102+
return new WildcardPattern(out);
103+
}
90104
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.esql.core.expression.predicate.regex;
8+
9+
import org.apache.lucene.util.automaton.Automaton;
10+
import org.apache.lucene.util.automaton.Operations;
11+
import org.elasticsearch.common.io.stream.NamedWriteable;
12+
import org.elasticsearch.common.io.stream.StreamInput;
13+
import org.elasticsearch.common.io.stream.StreamOutput;
14+
15+
import java.io.IOException;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import java.util.stream.Collectors;
19+
20+
import static org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
21+
22+
/**
23+
* Similar to basic regex, supporting '?' wildcard for single character (same as regex ".")
24+
* and '*' wildcard for multiple characters (same as regex ".*")
25+
* <p>
26+
* Allows escaping based on a regular char
27+
*
28+
*/
29+
public class WildcardPatternList extends AbstractStringPattern implements NamedWriteable {
30+
public static final Entry ENTRY = new Entry(
31+
WildcardPatternList.class,
32+
"WildcardPatternList",
33+
WildcardPatternList::new
34+
);
35+
public static final String NAME = "WildcardPatternList";
36+
private final List<WildcardPattern> patternList;
37+
38+
/*public WildcardPatternList(List<String> patterns) {
39+
this.patternList = patterns.stream().map(WildcardPattern::new).toList();
40+
}*/
41+
42+
public WildcardPatternList(List<WildcardPattern> patterns) {
43+
this.patternList = patterns;
44+
}
45+
46+
public WildcardPatternList(StreamInput in) throws IOException {
47+
this(in.readCollectionAsList(WildcardPattern::new));
48+
}
49+
50+
@Override
51+
public void writeTo(StreamOutput out) throws IOException {
52+
out.writeCollection(patternList, (o, pattern) -> pattern.writeTo(o));
53+
}
54+
55+
@Override
56+
public String getWriteableName() {
57+
return NAME;
58+
}
59+
public static WildcardPatternList readFrom(StreamInput in) throws IOException{
60+
return new WildcardPatternList(in.readCollectionAsList(WildcardPattern::readFrom));
61+
}
62+
63+
public List<WildcardPattern> patternList() {
64+
return patternList;
65+
}
66+
//public String pattern() {
67+
// return wildcard;
68+
//}
69+
70+
@Override
71+
public Automaton createAutomaton(boolean ignoreCase) {
72+
List<Automaton> automatonList = patternList.stream().map(x->x.createAutomaton(ignoreCase)).toList();
73+
return Operations.union(automatonList);
74+
}
75+
76+
@Override
77+
public String asJavaRegex() {
78+
return patternList.stream().map(WildcardPattern::asJavaRegex).collect(Collectors.joining("|"));
79+
}
80+
81+
@Override
82+
public String pattern() {
83+
return "(\"" + patternList.stream().map(WildcardPattern::pattern).collect(Collectors.joining("\", \"")) + "\")";
84+
}
85+
86+
/**
87+
* Returns the pattern in (Lucene) wildcard format.
88+
*/
89+
public String asLuceneWildcard() {
90+
throw new RuntimeException("LIKELIST does not have a Lucine Wildcard");
91+
}
92+
93+
94+
/**
95+
* Returns the pattern in (IndexNameExpressionResolver) wildcard format.
96+
*/
97+
//public String asIndexNameWildcard() {
98+
// return wildcard;
99+
//}
100+
101+
@Override
102+
public int hashCode() {
103+
return Objects.hash(patternList);
104+
}
105+
106+
@Override
107+
public boolean equals(Object obj) {
108+
if (this == obj) {
109+
return true;
110+
}
111+
112+
if (obj == null || getClass() != obj.getClass()) {
113+
return false;
114+
}
115+
116+
WildcardPatternList other = (WildcardPatternList) obj;
117+
return Objects.equals(patternList, other.patternList);
118+
}
119+
120+
}

x-pack/plugin/esql/qa/testFixtures/src/main/resources/where-like.csv-spec

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ emp_no:integer | first_name:keyword
7474
10013 | Eberhardt
7575
;
7676

77+
inLikeWithWildcard
78+
from employees | where first_name like ("Eberhardt*") | keep emp_no, first_name;
79+
80+
emp_no:integer | first_name:keyword
81+
10013 | Eberhardt
82+
;
83+
84+
inLikeTwoArgs
85+
from employees | where first_name like ("Eberhardt*", "testString") | keep emp_no, first_name;
86+
87+
emp_no:integer | first_name:keyword
88+
10013 | Eberhardt
89+
;
7790

7891
likeEvalNoWildcard
7992
from employees | eval x = concat(first_name, "X") | where x like "EberhardtX" | keep emp_no, first_name;

x-pack/plugin/esql/src/main/antlr/parser/Expression.g4

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ booleanExpression
1818
;
1919

2020
regexBooleanExpression
21-
: valueExpression (NOT)? kind=LIKE pattern=string
22-
| valueExpression (NOT)? kind=RLIKE pattern=string
21+
: valueExpression (NOT)? LIKE valueExpression #likeExpression
22+
| valueExpression (NOT)? RLIKE valueExpression #rlikeExpression
23+
| valueExpression (NOT)? LIKE LP valueExpression (COMMA valueExpression)* RP #logicalLikeList
2324
;
2425

2526
matchBooleanExpression

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Trim;
8383
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike;
8484
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLike;
85+
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLikeList;
8586
import org.elasticsearch.xpack.esql.expression.function.scalar.util.Delay;
8687
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
8788
import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull;
@@ -216,6 +217,7 @@ public static List<NamedWriteableRegistry.Entry> unaryScalars() {
216217
entries.add(ToVersion.ENTRY);
217218
entries.add(Trim.ENTRY);
218219
entries.add(WildcardLike.ENTRY);
220+
entries.add(WildcardLikeList.ENTRY);
219221
entries.add(Delay.ENTRY);
220222
// mv functions
221223
entries.addAll(MvFunctionWritables.getNamedWriteables());

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToLower;
179179
import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToUpper;
180180
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Trim;
181+
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLikeList;
181182
import org.elasticsearch.xpack.esql.expression.function.scalar.util.Delay;
182183
import org.elasticsearch.xpack.esql.parser.ParsingException;
183184
import org.elasticsearch.xpack.esql.session.Configuration;
@@ -417,6 +418,9 @@ private static FunctionDefinition[][] functions() {
417418
// IP
418419
new FunctionDefinition[] { def(CIDRMatch.class, CIDRMatch::new, "cidr_match") },
419420
new FunctionDefinition[] { def(IpPrefix.class, IpPrefix::new, "ip_prefix") },
421+
//likelist
422+
//new FunctionDefinition[] { def(WildcardLikeList.class, WildcardLikeList::new, "like_list") },
423+
420424
// conversion functions
421425
new FunctionDefinition[] {
422426
def(FromBase64.class, FromBase64::new, "from_base64"),
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function.scalar.string.regex;
9+
10+
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
11+
import org.elasticsearch.common.io.stream.StreamInput;
12+
import org.elasticsearch.common.io.stream.StreamOutput;
13+
import org.elasticsearch.xpack.esql.core.expression.Expression;
14+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
15+
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPatternList;
16+
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
17+
import org.elasticsearch.xpack.esql.core.querydsl.query.WildcardQuery;
18+
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
19+
import org.elasticsearch.xpack.esql.core.tree.Source;
20+
import org.elasticsearch.xpack.esql.expression.function.Example;
21+
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
22+
import org.elasticsearch.xpack.esql.expression.function.Param;
23+
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
24+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
25+
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
26+
27+
import java.io.IOException;
28+
29+
public class WildcardLikeList extends RegexMatch<WildcardPatternList> {
30+
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
31+
Expression.class,
32+
"WildcardLikeList",
33+
WildcardLikeList::new
34+
);
35+
public static final String NAME = "LIKELIST";
36+
37+
@FunctionInfo(returnType = "boolean", description = """
38+
Use `LIKE` to filter data based on string patterns using wildcards. `LIKE`
39+
usually acts on a field placed on the left-hand side of the operator, but it can
40+
also act on a constant (literal) expression. The right-hand side of the operator
41+
represents the pattern.
42+
43+
The following wildcard characters are supported:
44+
45+
* `*` matches zero or more characters.
46+
* `?` matches one character.""", detailedDescription = """
47+
Matching the exact characters `*` and `.` will require escaping.
48+
The escape character is backslash `\\`. Since also backslash is a special character in string literals,
49+
it will require further escaping.
50+
51+
<<load-esql-example, file=string tag=likeEscapingSingleQuotes>>
52+
53+
To reduce the overhead of escaping, we suggest using triple quotes strings `\"\"\"`
54+
55+
<<load-esql-example, file=string tag=likeEscapingTripleQuotes>>
56+
""", operator = NAME, examples = @Example(file = "docs", tag = "like"))
57+
public WildcardLikeList(
58+
Source source,
59+
@Param(name = "str", type = { "keyword", "text" }, description = "A literal expression.") Expression left,
60+
@Param(name = "pattern", type = { "keyword", "text" }, description = "Pattern.") WildcardPatternList patterns
61+
) {
62+
this(source, left, patterns, false);
63+
}
64+
65+
public WildcardLikeList(Source source, Expression left, WildcardPatternList patterns, boolean caseInsensitive) {
66+
super(source, left, patterns, caseInsensitive);
67+
}
68+
69+
public WildcardLikeList(StreamInput in) throws IOException {
70+
this(
71+
Source.readFrom((PlanStreamInput) in),
72+
in.readNamedWriteable(Expression.class),
73+
WildcardPatternList.readFrom(in),
74+
deserializeCaseInsensitivity(in)
75+
);
76+
}
77+
78+
79+
@Override
80+
public void writeTo(StreamOutput out) throws IOException {
81+
source().writeTo(out);
82+
out.writeNamedWriteable(field());
83+
pattern().writeTo(out);
84+
serializeCaseInsensitivity(out);
85+
}
86+
87+
@Override
88+
public String name() {
89+
return NAME;
90+
}
91+
92+
@Override
93+
public String getWriteableName() {
94+
return ENTRY.name;
95+
}
96+
97+
@Override
98+
protected NodeInfo<WildcardLikeList> info() {
99+
return NodeInfo.create(this, WildcardLikeList::new, field(), pattern(), caseInsensitive());
100+
}
101+
102+
@Override
103+
protected WildcardLikeList replaceChild(Expression newLeft) {
104+
return new WildcardLikeList(source(), newLeft, pattern(), caseInsensitive());
105+
}
106+
107+
@Override
108+
public Translatable translatable(LucenePushdownPredicates pushdownPredicates) {
109+
if(pattern().patternList().size() != 1){
110+
//we only support a single pattern in the list for pushdown for now
111+
return Translatable.NO;
112+
}
113+
return pushdownPredicates.isPushableAttribute(field()) ? Translatable.YES : Translatable.NO;
114+
115+
}
116+
117+
@Override
118+
public Query asQuery(LucenePushdownPredicates pushdownPredicates, TranslatorHandler handler) {
119+
var field = field();
120+
LucenePushdownPredicates.checkIsPushableAttribute(field);
121+
return translateField(handler.nameOf(field instanceof FieldAttribute fa ? fa.exactAttribute() : field));
122+
}
123+
124+
private Query translateField(String targetFieldName) {
125+
if (pattern().patternList().size() != 1) {
126+
throw new IllegalArgumentException("WildcardLikeList can only be translated when it has a single pattern");
127+
}
128+
return new WildcardQuery(source(), targetFieldName, pattern().patternList().getFirst().asLuceneWildcard(), caseInsensitive());
129+
}
130+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)