From e4c10d82bb5ec9be854f1068377722661e813968 Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Mon, 10 Jun 2024 12:03:28 -0700 Subject: [PATCH] Fix no match scorer time series source (#109545) The returned scorer can be null when the weight matches no document. --- ...TimeSeriesSortedSourceOperatorFactory.java | 7 ++- .../TimeSeriesSortedSourceOperatorTests.java | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorFactory.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorFactory.java index 887761fbd5a8b..8b52aa84aef21 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorFactory.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorFactory.java @@ -14,6 +14,7 @@ import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.PriorityQueue; @@ -361,7 +362,8 @@ static class Leaf { this.createdThread = Thread.currentThread(); tsids = leaf.reader().getSortedDocValues("_tsid"); timestamps = leaf.reader().getSortedNumericDocValues("@timestamp"); - iterator = weight.scorer(leaf).iterator(); + final Scorer scorer = weight.scorer(leaf); + iterator = scorer != null ? scorer.iterator() : DocIdSetIterator.empty(); } boolean nextDoc() throws IOException { @@ -384,7 +386,8 @@ void reinitializeIfNeeded(Thread executingThread) throws IOException { if (executingThread != createdThread) { tsids = leaf.reader().getSortedDocValues("_tsid"); timestamps = leaf.reader().getSortedNumericDocValues("@timestamp"); - iterator = weight.scorer(leaf).iterator(); + final Scorer scorer = weight.scorer(leaf); + iterator = scorer != null ? scorer.iterator() : DocIdSetIterator.empty(); if (docID != -1) { iterator.advance(docID); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorTests.java index 29e78f7abffde..17d302f198bff 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/TimeSeriesSortedSourceOperatorTests.java @@ -9,6 +9,7 @@ import org.apache.lucene.document.DoubleDocValuesField; import org.apache.lucene.document.FloatDocValuesField; +import org.apache.lucene.document.LongField; import org.apache.lucene.document.LongPoint; import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.document.SortedDocValuesField; @@ -18,6 +19,7 @@ import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.NoMergePolicy; import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; @@ -59,6 +61,7 @@ import java.util.function.Consumer; import java.util.function.Function; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.lessThanOrEqualTo; @@ -208,6 +211,57 @@ record Doc(int host, long timestamp, long metric) {} assertThat(offset, equalTo(Math.min(limit, numDocs))); } + public void testMatchNone() throws Exception { + long t0 = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parseMillis("2024-01-01T00:00:00Z"); + Sort sort = new Sort( + new SortField(TimeSeriesIdFieldMapper.NAME, SortField.Type.STRING, false), + new SortedNumericSortField(DataStreamTimestampFieldMapper.DEFAULT_PATH, SortField.Type.LONG, true) + ); + try ( + var directory = newDirectory(); + RandomIndexWriter writer = new RandomIndexWriter( + random(), + directory, + newIndexWriterConfig().setIndexSort(sort).setMergePolicy(NoMergePolicy.INSTANCE) + ) + ) { + int numDocs = between(1, 100); + long timestamp = t0; + int metrics = randomIntBetween(1, 3); + for (int i = 0; i < numDocs; i++) { + timestamp += between(1, 1000); + for (int j = 0; j < metrics; j++) { + String hostname = String.format(Locale.ROOT, "sensor-%02d", j); + writeTS(writer, timestamp, new Object[] { "sensor", hostname }, new Object[] { "voltage", j + 5 }); + } + } + try (var reader = writer.getReader()) { + var ctx = new LuceneSourceOperatorTests.MockShardContext(reader, 0); + Query query = randomFrom(LongField.newRangeQuery("@timestamp", 0, t0), new MatchNoDocsQuery()); + var timeSeriesFactory = TimeSeriesSortedSourceOperatorFactory.create( + Integer.MAX_VALUE, + randomIntBetween(1, 1024), + 1, + TimeValue.ZERO, + List.of(ctx), + unused -> query + ); + var driverContext = driverContext(); + List results = new ArrayList<>(); + OperatorTestCase.runDriver( + new Driver( + driverContext, + timeSeriesFactory.get(driverContext), + List.of(), + new TestResultPageSinkOperator(results::add), + () -> {} + ) + ); + assertThat(results, empty()); + } + } + } + @Override protected Operator.OperatorFactory simple() { return createTimeSeriesSourceOperator(directory, r -> this.reader = r, 1, 1, false, TimeValue.ZERO, writer -> {