Skip to content

Commit b1f6dfb

Browse files
committed
Don't build query if not ready
1 parent c8090e0 commit b1f6dfb

File tree

7 files changed

+120
-48
lines changed

7 files changed

+120
-48
lines changed

x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClustersIT.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ public void testLikeIndex() throws Exception {
418418
var columns = List.of(Map.of("name", "c", "type", "long"), Map.of("name", "_index", "type", "keyword"));
419419
var values = List.of(List.of(remoteDocs.size(), REMOTE_CLUSTER_NAME + ":" + remoteIndex));
420420
String resultString = Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(result));
421-
System.out.println(resultString);
421+
logger.info(resultString);
422422
assertResultMapForLike(includeCCSMetadata, result, columns, values, false, false);
423423
}
424424

@@ -433,7 +433,7 @@ public void testNotLikeIndex() throws Exception {
433433
var columns = List.of(Map.of("name", "c", "type", "long"), Map.of("name", "_index", "type", "keyword"));
434434
var values = List.of(List.of(localDocs.size(), localIndex));
435435
String resultString = Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(result));
436-
System.out.println(resultString);
436+
logger.info(resultString);
437437
assertResultMapForLike(includeCCSMetadata, result, columns, values, false, false);
438438
}
439439

@@ -448,7 +448,7 @@ public void testLikeListIndex() throws Exception {
448448
var columns = List.of(Map.of("name", "c", "type", "long"), Map.of("name", "_index", "type", "keyword"));
449449
var values = List.of(List.of(remoteDocs.size(), REMOTE_CLUSTER_NAME + ":" + remoteIndex));
450450
String resultString = Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(result));
451-
System.out.println(resultString);
451+
logger.info(resultString);
452452
assertResultMapForLike(includeCCSMetadata, result, columns, values, false, true);
453453
}
454454

@@ -463,7 +463,7 @@ public void testNotLikeListIndex() throws Exception {
463463
var columns = List.of(Map.of("name", "c", "type", "long"), Map.of("name", "_index", "type", "keyword"));
464464
var values = List.of(List.of(localDocs.size(), localIndex));
465465
String resultString = Strings.toString(JsonXContent.contentBuilder().prettyPrint().map(result));
466-
System.out.println(resultString);
466+
logger.info(resultString);
467467
assertResultMapForLike(includeCCSMetadata, result, columns, values, false, true);
468468
}
469469

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ protected WildcardLikeList replaceChild(Expression newLeft) {
9292
*/
9393
@Override
9494
public Translatable translatable(LucenePushdownPredicates pushdownPredicates) {
95-
return pushdownPredicates.isPushableAttribute(field()) ? Translatable.YES : Translatable.NO;
96-
95+
if (pushdownPredicates.minTransportVersion() == null) {
96+
return pushdownPredicates.isPushableAttribute(field()) ? Translatable.YES : Translatable.NO;
97+
} else {
98+
// The AutomatonQuery that we use right now isn't serializable.
99+
return Translatable.NO;
100+
}
97101
}
98102

99103
/**

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/LucenePushdownPredicates.java

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
99

10+
import org.elasticsearch.TransportVersion;
11+
import org.elasticsearch.core.Nullable;
12+
import org.elasticsearch.index.query.QueryBuilder;
1013
import org.elasticsearch.xpack.esql.core.expression.Expression;
1114
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
1215
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
@@ -30,6 +33,18 @@
3033
* </ol>
3134
*/
3235
public interface LucenePushdownPredicates {
36+
/**
37+
* If we're extracting a query for {@code can_match} then this is the
38+
* minimum transport version in the cluster. Otherwise, this is {@code null}.
39+
* <p>
40+
* If this is not null {@link Expression}s should not claim to be
41+
* serializable unless their {@link QueryBuilder}
42+
* {@link QueryBuilder#supportsVersion supports} the version.
43+
* </p>
44+
*/
45+
@Nullable
46+
TransportVersion minTransportVersion();
47+
3348
/**
3449
* For TEXT fields, we need to check if the field has a subfield of type KEYWORD that can be used instead.
3550
*/
@@ -101,29 +116,41 @@ static String pushableAttributeName(TypedAttribute attribute) {
101116
* In particular, it assumes TEXT fields have no exact subfields (underlying keyword field),
102117
* and that isAggregatable means indexed and has hasDocValues.
103118
*/
104-
LucenePushdownPredicates DEFAULT = new LucenePushdownPredicates() {
105-
@Override
106-
public boolean hasExactSubfield(FieldAttribute attr) {
107-
return false;
108-
}
119+
LucenePushdownPredicates DEFAULT = forCanMatch(null);
109120

110-
@Override
111-
public boolean isIndexedAndHasDocValues(FieldAttribute attr) {
112-
// Is the FieldType.isAggregatable() check correct here? In FieldType isAggregatable usually only means hasDocValues
113-
return attr.field().isAggregatable();
114-
}
121+
/**
122+
* A {@link LucenePushdownPredicates} for use with the {@code can_match} phase.
123+
*/
124+
static LucenePushdownPredicates forCanMatch(TransportVersion minTransportVersion) {
125+
return new LucenePushdownPredicates() {
126+
@Override
127+
public TransportVersion minTransportVersion() {
128+
return minTransportVersion;
129+
}
115130

116-
@Override
117-
public boolean isIndexed(FieldAttribute attr) {
118-
// TODO: This is the original behaviour, but is it correct? In FieldType isAggregatable usually only means hasDocValues
119-
return attr.field().isAggregatable();
120-
}
131+
@Override
132+
public boolean hasExactSubfield(FieldAttribute attr) {
133+
return false;
134+
}
121135

122-
@Override
123-
public boolean canUseEqualityOnSyntheticSourceDelegate(FieldAttribute attr, String value) {
124-
return false;
125-
}
126-
};
136+
@Override
137+
public boolean isIndexedAndHasDocValues(FieldAttribute attr) {
138+
// Is the FieldType.isAggregatable() check correct here? In FieldType isAggregatable usually only means hasDocValues
139+
return attr.field().isAggregatable();
140+
}
141+
142+
@Override
143+
public boolean isIndexed(FieldAttribute attr) {
144+
// TODO: This is the original behaviour, but is it correct? In FieldType isAggregatable usually only means hasDocValues
145+
return attr.field().isAggregatable();
146+
}
147+
148+
@Override
149+
public boolean canUseEqualityOnSyntheticSourceDelegate(FieldAttribute attr, String value) {
150+
return false;
151+
}
152+
};
153+
}
127154

128155
/**
129156
* If we have access to {@link SearchStats} over a collection of shards, we can make more fine-grained decisions about what can be
@@ -133,6 +160,11 @@ static LucenePushdownPredicates from(SearchStats stats) {
133160
// TODO: use FieldAttribute#fieldName, otherwise this doesn't apply to field attributes used for union types.
134161
// C.f. https://github.com/elastic/elasticsearch/issues/128905
135162
return new LucenePushdownPredicates() {
163+
@Override
164+
public TransportVersion minTransportVersion() {
165+
return null;
166+
}
167+
136168
@Override
137169
public boolean hasExactSubfield(FieldAttribute attr) {
138170
return stats.hasExactSubfield(new FieldAttribute.FieldName(attr.name()));

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/PlannerUtils.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.esql.planner;
99

10+
import org.elasticsearch.TransportVersion;
1011
import org.elasticsearch.common.Strings;
1112
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
1213
import org.elasticsearch.common.util.BigArrays;
@@ -212,20 +213,23 @@ public static PhysicalPlan localPlan(
212213
/**
213214
* Extracts a filter that can be used to skip unmatched shards on the coordinator.
214215
*/
215-
public static QueryBuilder canMatchFilter(PhysicalPlan plan) {
216-
return detectFilter(plan, CoordinatorRewriteContext.SUPPORTED_FIELDS::contains);
216+
public static QueryBuilder canMatchFilter(TransportVersion minTransportVersion, PhysicalPlan plan) {
217+
return detectFilter(minTransportVersion, plan, CoordinatorRewriteContext.SUPPORTED_FIELDS::contains);
217218
}
218219

219220
/**
220221
* Note that since this filter does not have access to SearchStats, it cannot detect if the field is a text field with a delegate.
221222
* We currently only use this filter for the @timestamp field, which is always a date field. Any tests that wish to use this should
222223
* take care to not use it with TEXT fields.
223224
*/
224-
static QueryBuilder detectFilter(PhysicalPlan plan, Predicate<String> fieldName) {
225+
static QueryBuilder detectFilter(TransportVersion minTransportVersion, PhysicalPlan plan, Predicate<String> fieldName) {
225226
// first position is the REST filter, the second the query filter
226227
final List<QueryBuilder> requestFilters = new ArrayList<>();
228+
final LucenePushdownPredicates ctx = LucenePushdownPredicates.forCanMatch(minTransportVersion);
227229
plan.forEachDown(FragmentExec.class, fe -> {
228-
requestFilters.add(fe.esFilter());
230+
if (fe.esFilter() != null && fe.esFilter().supportsVersion(minTransportVersion)) {
231+
requestFilters.add(fe.esFilter());
232+
}
229233
// detect filter inside the query
230234
fe.fragment().forEachUp(Filter.class, f -> {
231235
// the only filter that can be pushed down is that on top of the relation
@@ -243,15 +247,13 @@ static QueryBuilder detectFilter(PhysicalPlan plan, Predicate<String> fieldName)
243247
// and the expression is pushable (functions can be fully translated)
244248
if (matchesField
245249
&& refsBuilder.isEmpty()
246-
&& translatable(exp, LucenePushdownPredicates.DEFAULT).finish() == TranslationAware.FinishedTranslatable.YES) {
250+
&& translatable(exp, ctx).finish() == TranslationAware.FinishedTranslatable.YES) {
247251
matches.add(exp);
248252
}
249253
}
250254
}
251255
if (matches.isEmpty() == false) {
252-
requestFilters.add(
253-
TRANSLATOR_HANDLER.asQuery(LucenePushdownPredicates.DEFAULT, Predicates.combineAnd(matches)).toQueryBuilder()
254-
);
256+
requestFilters.add(TRANSLATOR_HANDLER.asQuery(ctx, Predicates.combineAnd(matches)).toQueryBuilder());
255257
}
256258
});
257259
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ void startComputeOnDataNodes(
116116
esqlExecutor,
117117
parentTask,
118118
originalIndices,
119-
PlannerUtils.canMatchFilter(dataNodePlan),
119+
PlannerUtils.canMatchFilter(clusterService.state().getMinTransportVersion(), dataNodePlan),
120120
clusterAlias,
121121
configuration.allowPartialResults(),
122122
maxConcurrentNodesPerCluster == null ? -1 : maxConcurrentNodesPerCluster,

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
1212

1313
import org.apache.lucene.util.BytesRef;
14+
import org.elasticsearch.TransportVersion;
15+
import org.elasticsearch.xpack.esql.EsqlTestUtils;
16+
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
1417
import org.elasticsearch.xpack.esql.core.expression.Expression;
1518
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
1619
import org.elasticsearch.xpack.esql.core.expression.Literal;
@@ -22,6 +25,7 @@
2225
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
2326
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
2427
import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLikeList;
28+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
2529

2630
import java.util.ArrayList;
2731
import java.util.List;
@@ -96,4 +100,20 @@ static Expression buildWildcardLikeList(Source source, List<Expression> args) {
96100
? new WildcardLikeList(source, expression, wildcardPatternList)
97101
: new WildcardLikeList(source, expression, wildcardPatternList, false));
98102
}
103+
104+
public void testNotPushableOverCanMatch() {
105+
TranslationAware translatable = (TranslationAware) buildFieldExpression(testCase);
106+
assertThat(
107+
translatable.translatable(LucenePushdownPredicates.forCanMatch(TransportVersion.current())).finish(),
108+
equalTo(TranslationAware.FinishedTranslatable.NO)
109+
);
110+
}
111+
112+
public void testPushable() {
113+
TranslationAware translatable = (TranslationAware) buildFieldExpression(testCase);
114+
assertThat(
115+
translatable.translatable(LucenePushdownPredicates.from(new EsqlTestUtils.TestSearchStats())).finish(),
116+
equalTo(TranslationAware.FinishedTranslatable.YES)
117+
);
118+
}
99119
}

0 commit comments

Comments
 (0)