2021-12-23 02:13:51 +01:00
|
|
|
package it.cavallium.dbengine.lucene.searcher;
|
|
|
|
|
2021-12-23 02:20:40 +01:00
|
|
|
import static it.cavallium.dbengine.client.UninterruptibleScheduler.uninterruptibleScheduler;
|
|
|
|
|
2021-12-23 02:13:51 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.UncheckedIOException;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
import org.apache.lucene.index.LeafReader;
|
|
|
|
import org.apache.lucene.index.LeafReaderContext;
|
|
|
|
import org.apache.lucene.search.DocIdSetIterator;
|
|
|
|
import org.apache.lucene.search.IndexSearcher;
|
|
|
|
import org.apache.lucene.search.Query;
|
|
|
|
import org.apache.lucene.search.ScoreDoc;
|
|
|
|
import org.apache.lucene.search.ScoreMode;
|
|
|
|
import org.apache.lucene.search.Scorer;
|
|
|
|
import org.apache.lucene.search.Weight;
|
|
|
|
import org.apache.lucene.util.Bits;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import org.reactivestreams.Publisher;
|
|
|
|
import reactor.core.publisher.Flux;
|
|
|
|
import reactor.core.scheduler.Schedulers;
|
|
|
|
|
|
|
|
public class LuceneGenerator implements Supplier<ScoreDoc> {
|
|
|
|
|
|
|
|
private final IndexSearcher shard;
|
|
|
|
private final int shardIndex;
|
|
|
|
private final Query query;
|
|
|
|
private final Iterator<LeafReaderContext> leavesIterator;
|
|
|
|
private final boolean computeScores;
|
|
|
|
private final Float minScore;
|
|
|
|
|
|
|
|
private long remainingOffset;
|
|
|
|
private long remainingAllowedResults;
|
|
|
|
private Weight weight;
|
|
|
|
|
|
|
|
private LeafReaderContext leaf;
|
|
|
|
private DocIdSetIterator docIdSetIterator;
|
|
|
|
private Scorer scorer;
|
|
|
|
|
|
|
|
LuceneGenerator(IndexSearcher shard, LocalQueryParams localQueryParams, int shardIndex) {
|
|
|
|
this.shard = shard;
|
|
|
|
this.shardIndex = shardIndex;
|
|
|
|
this.query = localQueryParams.query();
|
|
|
|
this.remainingOffset = localQueryParams.offsetLong();
|
|
|
|
this.remainingAllowedResults = localQueryParams.limitLong();
|
|
|
|
this.computeScores = localQueryParams.needsScores() || localQueryParams.minCompetitiveScore() != null;
|
|
|
|
this.minScore = localQueryParams.minCompetitiveScore();
|
|
|
|
List<LeafReaderContext> leaves = shard.getTopReaderContext().leaves();
|
|
|
|
this.leavesIterator = leaves.iterator();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Flux<ScoreDoc> reactive(IndexSearcher shard, LocalQueryParams localQueryParams, int shardIndex) {
|
|
|
|
return Flux
|
|
|
|
.<ScoreDoc, LuceneGenerator>generate(() -> new LuceneGenerator(shard, localQueryParams, shardIndex),
|
|
|
|
(s, sink) -> {
|
|
|
|
var val = s.get();
|
|
|
|
if (val == null) {
|
|
|
|
sink.complete();
|
|
|
|
} else {
|
|
|
|
sink.next(val);
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
)
|
2021-12-23 02:20:40 +01:00
|
|
|
.subscribeOn(uninterruptibleScheduler(Schedulers.boundedElastic()), false);
|
2021-12-23 02:13:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ScoreDoc get() {
|
|
|
|
while (remainingOffset > 0) {
|
|
|
|
skipNext();
|
|
|
|
}
|
|
|
|
if (remainingAllowedResults == 0) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
remainingAllowedResults--;
|
|
|
|
}
|
|
|
|
return getNext();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void skipNext() {
|
|
|
|
getNext();
|
|
|
|
remainingOffset--;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Weight createWeight() throws IOException {
|
|
|
|
ScoreMode scoreMode = computeScores ? ScoreMode.COMPLETE : ScoreMode.COMPLETE_NO_SCORES;
|
|
|
|
return shard.createWeight(shard.rewrite(query), scoreMode, 1f);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ScoreDoc getNext() {
|
|
|
|
if (weight == null) {
|
|
|
|
try {
|
|
|
|
weight = createWeight();
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new UncheckedIOException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return getWeightedNext();
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new UncheckedIOException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private ScoreDoc getWeightedNext() throws IOException {
|
|
|
|
while (tryAdvanceDocIdSetIterator()) {
|
|
|
|
LeafReader reader = leaf.reader();
|
|
|
|
Bits liveDocs = reader.getLiveDocs();
|
|
|
|
int doc;
|
|
|
|
while ((doc = docIdSetIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
|
|
|
if (docDeleted(liveDocs, doc) || belowMinScore(scorer)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return transformDoc(doc);
|
|
|
|
}
|
|
|
|
docIdSetIterator = null;
|
|
|
|
}
|
|
|
|
clearState();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
private boolean tryAdvanceDocIdSetIterator() throws IOException {
|
|
|
|
if (docIdSetIterator != null) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
while (leavesIterator.hasNext()) {
|
|
|
|
LeafReaderContext leaf = leavesIterator.next();
|
|
|
|
Scorer scorer = weight.scorer(leaf);
|
|
|
|
if (scorer == null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.scorer = scorer;
|
|
|
|
this.leaf = leaf;
|
|
|
|
this.docIdSetIterator = scorer.iterator();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private ScoreDoc transformDoc(int doc) throws IOException {
|
|
|
|
return new ScoreDoc(leaf.docBase + doc, scorer.score(), shardIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean docDeleted(@Nullable Bits liveDocs, int doc) {
|
|
|
|
if (liveDocs == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !liveDocs.get(doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean belowMinScore(Scorer currentScorer) throws IOException {
|
|
|
|
return minScore != null && currentScorer.score() < minScore;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void clearState() {
|
|
|
|
docIdSetIterator = null;
|
|
|
|
scorer = null;
|
|
|
|
leaf = null;
|
|
|
|
}
|
|
|
|
}
|