Skip to content

Commit 5b31d78

Browse files
committed
Implement Lucene pushdown on EndsWith
1 parent 22d1cc4 commit 5b31d78

File tree

2 files changed

+70
-1
lines changed
  • x-pack/plugin/esql

2 files changed

+70
-1
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,45 @@ Bernatsky |false
12391239
;
12401240

12411241

1242+
endsWithLucenePushdown
1243+
1244+
from hosts
1245+
| where ends_with(host, "ta") and ends_with(host_group, "cluster")
1246+
| keep host, host_group
1247+
| sort host, host_group;
1248+
1249+
host:keyword | host_group:text
1250+
beta | Kubernetes cluster
1251+
beta | Kubernetes cluster
1252+
beta | Kubernetes cluster
1253+
;
1254+
1255+
endsWithLuceneDisabledPushdown
1256+
1257+
from hosts
1258+
| where host == "unknown host" or (ends_with(host, "ta") and ends_with(host_group, "cluster"))
1259+
| keep host, host_group
1260+
| sort host, host_group;
1261+
1262+
host:keyword | host_group:text
1263+
beta | Kubernetes cluster
1264+
beta | Kubernetes cluster
1265+
beta | Kubernetes cluster
1266+
;
1267+
1268+
endsWithLucenePushdownIgnoreMultivalues
1269+
1270+
from hosts
1271+
| where ends_with(description, "host")
1272+
| keep description
1273+
| sort description;
1274+
1275+
warning:Line 2:9: evaluation of [ends_with(description, \"host\")] failed, treating result as null. Only first 20 failures recorded.
1276+
warning:Line 2:9: java.lang.IllegalArgumentException: single-value function encountered multi-value
1277+
1278+
description:text
1279+
;
1280+
12421281

12431282
toLowerRow#[skip:-8.12.99]
12441283
// tag::to_lower[]

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWith.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77

88
package org.elasticsearch.xpack.esql.expression.function.scalar.string;
99

10+
import org.apache.lucene.queryparser.classic.QueryParser;
1011
import org.apache.lucene.util.BytesRef;
1112
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1213
import org.elasticsearch.common.io.stream.StreamInput;
1314
import org.elasticsearch.common.io.stream.StreamOutput;
15+
import org.elasticsearch.common.lucene.BytesRefs;
1416
import org.elasticsearch.compute.ann.Evaluator;
1517
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
18+
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
1619
import org.elasticsearch.xpack.esql.core.expression.Expression;
20+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
21+
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
22+
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
23+
import org.elasticsearch.xpack.esql.core.querydsl.query.WildcardQuery;
1724
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
1825
import org.elasticsearch.xpack.esql.core.tree.Source;
1926
import org.elasticsearch.xpack.esql.core.type.DataType;
@@ -22,6 +29,8 @@
2229
import org.elasticsearch.xpack.esql.expression.function.Param;
2330
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
2431
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
32+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
33+
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
2534

2635
import java.io.IOException;
2736
import java.util.Arrays;
@@ -31,7 +40,7 @@
3140
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
3241
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
3342

34-
public class EndsWith extends EsqlScalarFunction {
43+
public class EndsWith extends EsqlScalarFunction implements TranslationAware.SingleValueTranslationAware {
3544
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "EndsWith", EndsWith::new);
3645

3746
private final Expression str;
@@ -129,6 +138,27 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
129138
return new EndsWithEvaluator.Factory(source(), toEvaluator.apply(str), toEvaluator.apply(suffix));
130139
}
131140

141+
@Override
142+
public boolean translatable(LucenePushdownPredicates pushdownPredicates) {
143+
return pushdownPredicates.isPushableAttribute(str) && suffix.foldable();
144+
}
145+
146+
@Override
147+
public Query asQuery(TranslatorHandler handler) {
148+
LucenePushdownPredicates.checkIsPushableAttribute(str);
149+
var fieldName = handler.nameOf(str instanceof FieldAttribute fa ? fa.exactAttribute() : str);
150+
151+
// TODO: Get the real FoldContext here
152+
var wildcardQuery = "*" + QueryParser.escape(BytesRefs.toString(suffix.fold(FoldContext.small())));
153+
154+
return new WildcardQuery(source(), fieldName, wildcardQuery);
155+
}
156+
157+
@Override
158+
public Expression singleValueField() {
159+
return str;
160+
}
161+
132162
Expression str() {
133163
return str;
134164
}

0 commit comments

Comments
 (0)