Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ public Relation isFieldWithinQuery(
++fromInclusive;
}
// we set the time range filter from during rewrite, because this may be the only time we ever parse it,
// in case the shard if filtered out and does not run the query phase or all its docs are within the bounds.
// in case the shard gets filtered out and does not run the query phase or all its docs are within the bounds.
context.setTimeRangeFilterFromMillis(fieldName, fromInclusive, resolution);
}

Expand Down
55 changes: 30 additions & 25 deletions server/src/main/java/org/elasticsearch/search/SearchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -721,13 +721,7 @@ public void executeQueryPhase(ShardSearchRequest request, CancellableTask task,
if (orig.canReturnNullResponseIfMatchNoDocs()) {
assert orig.scroll() == null;
ShardSearchRequest clone = new ShardSearchRequest(orig);
CanMatchContext canMatchContext = new CanMatchContext(
clone,
indicesService::indexServiceSafe,
this::findReaderContext,
defaultKeepAlive,
maxKeepAlive
);
CanMatchContext canMatchContext = createCanMatchContext(clone);
CanMatchShardResponse canMatchResp = canMatch(canMatchContext, false);
if (canMatchResp.canMatch() == false) {
l.onResponse(QuerySearchResult.nullInstance());
Expand Down Expand Up @@ -1942,8 +1936,10 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
final IndexService indexService = indicesService.indexServiceSafe(shardSearchRequest.shardId().getIndex());
final IndexShard indexShard = indexService.getShard(shardSearchRequest.shardId().id());
try {
// TODO remove the exception handling as it's now in canMatch itself
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatch(shardSearchRequest)));
// TODO remove the exception handling as it's now in canMatch itself - the failure no longer needs to be serialized
CanMatchContext canMatchContext = createCanMatchContext(shardSearchRequest);
CanMatchShardResponse canMatchShardResponse = canMatch(canMatchContext, true);
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatchShardResponse));
indexShard.getSearchOperationListener().onCanMatchPhase(System.nanoTime() - shardCanMatchStartTimeInNanos);
} catch (Exception e) {
responses.add(new CanMatchNodeResponse.ResponseOrFailure(e));
Expand All @@ -1958,16 +1954,14 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
* won't match any documents on the current shard. Exceptions are handled within the method, and never re-thrown.
*/
public CanMatchShardResponse canMatch(ShardSearchRequest request) {
CanMatchContext canMatchContext = new CanMatchContext(
request,
indicesService::indexServiceSafe,
this::findReaderContext,
defaultKeepAlive,
maxKeepAlive
);
CanMatchContext canMatchContext = createCanMatchContext(request);
return canMatch(canMatchContext, true);
}

CanMatchContext createCanMatchContext(ShardSearchRequest request) {
return new CanMatchContext(request, indicesService::indexServiceSafe, this::findReaderContext, defaultKeepAlive, maxKeepAlive);
}

static class CanMatchContext {
private final ShardSearchRequest request;
private final Function<Index, IndexService> indexServiceLookup;
Expand All @@ -1977,6 +1971,8 @@ static class CanMatchContext {

private IndexService indexService;

private Long timeRangeFilterFromMillis;

CanMatchContext(
ShardSearchRequest request,
Function<Index, IndexService> indexServiceLookup,
Expand Down Expand Up @@ -2024,12 +2020,21 @@ IndexService getIndexService() {
}
return this.indexService;
}

void setTimeRangeFilterFromMillis(Long timeRangeFilterFromMillis) {
this.timeRangeFilterFromMillis = timeRangeFilterFromMillis;
}

Long getTimeRangeFilterFromMillis() {
return timeRangeFilterFromMillis;
}
}

static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean checkRefreshPending) {
assert canMatchContext.request.searchType() == SearchType.QUERY_THEN_FETCH
: "unexpected search type: " + canMatchContext.request.searchType();
Releasable releasable = null;
QueryRewriteContext queryRewriteContext = null;
try {
IndexService indexService;
final boolean hasRefreshPending;
Expand All @@ -2042,7 +2047,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
readerContext = canMatchContext.findReaderContext();
releasable = readerContext.markAsUsed(canMatchContext.getKeepAlive());
indexService = readerContext.indexService();
QueryRewriteContext queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
return new CanMatchShardResponse(false, null);
}
Expand All @@ -2051,10 +2056,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
if (canMatchContext.request.readerId().isRetryable() == false) {
return new CanMatchShardResponse(true, null);
}
if (queryStillMatchesAfterRewrite(
canMatchContext.request,
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
) == false) {
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
return new CanMatchShardResponse(false, null);
}
final Engine.SearcherSupplier searcherSupplier = canMatchContext.getShard().acquireSearcherSupplier();
Expand All @@ -2067,10 +2070,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
}
canMatchSearcher = searcher;
} else {
if (queryStillMatchesAfterRewrite(
canMatchContext.request,
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
) == false) {
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
return new CanMatchShardResponse(false, null);
}
boolean needsWaitForRefresh = canMatchContext.request.waitForCheckpoint() != UNASSIGNED_SEQ_NO;
Expand All @@ -2083,6 +2084,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
}
try (canMatchSearcher) {
SearchExecutionContext context = canMatchContext.getSearchExecutionContext(canMatchSearcher);
queryRewriteContext = context;
final boolean canMatch = queryStillMatchesAfterRewrite(canMatchContext.request, context);
if (canMatch || hasRefreshPending) {
FieldSortBuilder sortBuilder = FieldSortBuilder.getPrimaryFieldSortOrNull(canMatchContext.request.source());
Expand All @@ -2094,6 +2096,9 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
} catch (Exception e) {
return new CanMatchShardResponse(true, null);
} finally {
if (queryRewriteContext != null) {
canMatchContext.setTimeRangeFilterFromMillis(queryRewriteContext.getTimeRangeFilterFromMillis());
}
Releasables.close(releasable);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.LeafFieldData;
import org.elasticsearch.index.mapper.DateFieldMapper;
import org.elasticsearch.index.mapper.KeywordFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperBuilderContext;
Expand All @@ -43,10 +44,12 @@
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.index.shard.IndexShardTestCase;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.script.ScriptCompiler;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.internal.AliasFilter;
Expand All @@ -61,7 +64,10 @@
import org.elasticsearch.xcontent.XContentParserConfiguration;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -112,6 +118,17 @@ public void testCanMatchKeywordSortedQueryMatchAll() throws IOException {
doTestCanMatch(searchRequest, sortField, true, expectedMinAndMax, false);
}

public void testCanMatchTimeRangeFilter() throws IOException {
SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(false)
.source(new SearchSourceBuilder().query(new RangeQueryBuilder("@timestamp").from("2025-12-15")));
SearchService.CanMatchContext canMatchContext = doTestCanMatch(searchRequest, null, false, null, false);
LocalDateTime localDateTime = LocalDateTime.of(2025, 12, 15, 0, 0);
assertEquals(
localDateTime.atZone(ZoneOffset.UTC).toInstant().toEpochMilli(),
canMatchContext.getTimeRangeFilterFromMillis().longValue()
);
}

public void testCanMatchKeywordSortedQueryMatchNoneWithException() throws IOException {
SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(false)
.source(new SearchSourceBuilder().sort("field").query(new MatchNoneQueryBuilder()));
Expand Down Expand Up @@ -370,7 +387,7 @@ public void testIsExecutorQueuedBeyondPrewarmingFactor() throws InterruptedExcep
}
}

private void doTestCanMatch(
private SearchService.CanMatchContext doTestCanMatch(
SearchRequest searchRequest,
SortField sortField,
boolean expectedCanMatch,
Expand All @@ -392,7 +409,7 @@ private void doTestCanMatch(
IndexShard indexShard = newShard(true);
try {
recoverShardFromStore(indexShard);
assertTrue(indexDoc(indexShard, "_doc", "id", "{\"field\":\"value\"}").isCreated());
assertTrue(indexDoc(indexShard, "_doc", "id", "{\"field\":\"value\", \"@timestamp\":\"2025-12-09\"}").isCreated());
assertTrue(indexShard.refresh("test").refreshed());
try (Engine.Searcher searcher = indexShard.acquireSearcher("test")) {
SearchExecutionContext searchExecutionContext = createSearchExecutionContext(
Expand All @@ -416,7 +433,7 @@ private void doTestCanMatch(
assertEquals(expectedMinAndMax.getMin(), minAndMax.getMin());
assertEquals(expectedMinAndMax.getMin(), minAndMax.getMax());
}

return canMatchContext;
}
} finally {
closeShards(indexShard);
Expand All @@ -443,9 +460,16 @@ private SearchExecutionContext createSearchExecutionContext(
Collections.emptyMap()
);
KeywordFieldMapper keywordFieldMapper = new KeywordFieldMapper.Builder("field", IndexVersion.current()).build(root);
DateFieldMapper dateFieldMapper = new DateFieldMapper.Builder(
"@timestamp",
DateFieldMapper.Resolution.MILLISECONDS,
null,
ScriptCompiler.NONE,
indexSettings
).build(root);
MappingLookup mappingLookup = MappingLookup.fromMappers(
mapping,
Collections.singletonList(keywordFieldMapper),
List.of(keywordFieldMapper, dateFieldMapper),
Collections.emptyList()
);
return new SearchExecutionContext(
Expand Down