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
6 changes: 6 additions & 0 deletions docs/changelog/89962.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 89962
summary: Empty intervals needs to start in position -1
area: Search
type: bug
issues:
- 89789
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.search.query;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.analysis.AnalyzerProvider;
import org.elasticsearch.index.analysis.AnalyzerScope;
import org.elasticsearch.index.query.IntervalQueryBuilder;
import org.elasticsearch.index.query.IntervalsSourceProvider;
import org.elasticsearch.indices.analysis.AnalysisModule;
import org.elasticsearch.plugins.AnalysisPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.InternalSettingsPlugin;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import static java.util.Collections.singletonMap;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;

public class IntervalQueriesIT extends ESIntegTestCase {

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Arrays.asList(InternalSettingsPlugin.class, MockAnalysisPlugin.class);
}

public void testEmptyIntervalsWithNestedMappings() throws InterruptedException {
assertAcked(prepareCreate("nested").setMapping("""
{ "_doc" : {
"properties" : {
"empty_text" : { "type" : "text", "analyzer" : "empty" },
"text" : { "type" : "text" },
"nested" : { "type" : "nested", "properties" : { "nt" : { "type" : "text" } } }
}
}}
"""));

indexRandom(
true,
client().prepareIndex("nested").setId("1").setSource("text", "the quick brown fox jumps"),
client().prepareIndex("nested").setId("2").setSource("text", "quick brown"),
client().prepareIndex("nested").setId("3").setSource("text", "quick")
);

SearchResponse resp = client().prepareSearch("nested")
.setQuery(
new IntervalQueryBuilder("empty_text", new IntervalsSourceProvider.Match("an empty query", 0, true, null, null, null))
)
.get();
assertEquals(0, resp.getFailedShards());
}

private static class EmptyAnalyzer extends Analyzer {

@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer source = new KeywordTokenizer();
TokenStream sink = new TokenStream() {
@Override
public boolean incrementToken() throws IOException {
return false;
}
};
return new TokenStreamComponents(source, sink);
}
}

public static class MockAnalysisPlugin extends Plugin implements AnalysisPlugin {

@Override
public Map<String, AnalysisModule.AnalysisProvider<AnalyzerProvider<? extends Analyzer>>> getAnalyzers() {
return singletonMap("empty", (indexSettings, environment, name, settings) -> new AnalyzerProvider<>() {
@Override
public String name() {
return "empty";
}

@Override
public AnalyzerScope scope() {
return AnalyzerScope.GLOBAL;
}

@Override
public Analyzer get() {
return new EmptyAnalyzer();
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ protected List<IntervalsSource> analyzeGraph(TokenStream source) throws IOExcept
@Override
public IntervalIterator intervals(String field, LeafReaderContext ctx) {
return new IntervalIterator() {
boolean exhausted = false;

@Override
public int start() {
return NO_MORE_INTERVALS;
Expand Down Expand Up @@ -252,16 +254,18 @@ public float matchCost() {

@Override
public int docID() {
return NO_MORE_DOCS;
return exhausted ? NO_MORE_DOCS : -1;
}

@Override
public int nextDoc() {
exhausted = true;
return NO_MORE_DOCS;
}

@Override
public int advance(int target) {
exhausted = true;
return NO_MORE_DOCS;
}

Expand Down