blob: 90b11aaa8903e090748d15e5010b0d3a9ead54dc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.search;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
/** A Scorer for queries with a required subscorer
* and an excluding (prohibited) sub {@link Scorer}.
*/
class ReqExclScorer extends Scorer {
private final Scorer reqScorer;
// approximations of the scorers, or the scorers themselves if they don't support approximations
private final DocIdSetIterator reqApproximation;
private final DocIdSetIterator exclApproximation;
// two-phase views of the scorers, or null if they do not support approximations
private final TwoPhaseIterator reqTwoPhaseIterator;
private final TwoPhaseIterator exclTwoPhaseIterator;
/** Construct a <code>ReqExclScorer</code>.
* @param reqScorer The scorer that must match, except where
* @param exclScorer indicates exclusion.
*/
public ReqExclScorer(Scorer reqScorer, Scorer exclScorer) {
super(reqScorer.weight);
this.reqScorer = reqScorer;
reqTwoPhaseIterator = reqScorer.twoPhaseIterator();
if (reqTwoPhaseIterator == null) {
reqApproximation = reqScorer.iterator();
} else {
reqApproximation = reqTwoPhaseIterator.approximation();
}
exclTwoPhaseIterator = exclScorer.twoPhaseIterator();
if (exclTwoPhaseIterator == null) {
exclApproximation = exclScorer.iterator();
} else {
exclApproximation = exclTwoPhaseIterator.approximation();
}
}
/** Confirms whether or not the given {@link TwoPhaseIterator}
* matches on the current document. */
private static boolean matchesOrNull(TwoPhaseIterator it) throws IOException {
return it == null || it.matches();
}
@Override
public DocIdSetIterator iterator() {
return TwoPhaseIterator.asDocIdSetIterator(twoPhaseIterator());
}
@Override
public int docID() {
return reqApproximation.docID();
}
@Override
public float score() throws IOException {
return reqScorer.score(); // reqScorer may be null when next() or skipTo() already return false
}
@Override
public int advanceShallow(int target) throws IOException {
return reqScorer.advanceShallow(target);
}
@Override
public float getMaxScore(int upTo) throws IOException {
return reqScorer.getMaxScore(upTo);
}
@Override
public void setMinCompetitiveScore(float score) throws IOException {
// The score of this scorer is the same as the score of 'reqScorer'.
reqScorer.setMinCompetitiveScore(score);
}
@Override
public Collection<ChildScorable> getChildren() {
return Collections.singleton(new ChildScorable(reqScorer, "MUST"));
}
/**
* Estimation of the number of operations required to call DISI.advance.
* This is likely completely wrong, especially given that the cost of
* this method usually depends on how far you want to advance, but it's
* probably better than nothing.
*/
private static final int ADVANCE_COST = 10;
private static float matchCost(
DocIdSetIterator reqApproximation,
TwoPhaseIterator reqTwoPhaseIterator,
DocIdSetIterator exclApproximation,
TwoPhaseIterator exclTwoPhaseIterator) {
float matchCost = 2; // we perform 2 comparisons to advance exclApproximation
if (reqTwoPhaseIterator != null) {
// this two-phase iterator must always be matched
matchCost += reqTwoPhaseIterator.matchCost();
}
// match cost of the prohibited clause: we need to advance the approximation
// and match the two-phased iterator
final float exclMatchCost = ADVANCE_COST
+ (exclTwoPhaseIterator == null ? 0 : exclTwoPhaseIterator.matchCost());
// upper value for the ratio of documents that reqApproximation matches that
// exclApproximation also matches
float ratio;
if (reqApproximation.cost() <= 0) {
ratio = 1f;
} else if (exclApproximation.cost() <= 0) {
ratio = 0f;
} else {
ratio = (float) Math.min(reqApproximation.cost(), exclApproximation.cost()) / reqApproximation.cost();
}
matchCost += ratio * exclMatchCost;
return matchCost;
}
@Override
public TwoPhaseIterator twoPhaseIterator() {
final float matchCost = matchCost(reqApproximation, reqTwoPhaseIterator, exclApproximation, exclTwoPhaseIterator);
if (reqTwoPhaseIterator == null
|| (exclTwoPhaseIterator != null && reqTwoPhaseIterator.matchCost() <= exclTwoPhaseIterator.matchCost())) {
// reqTwoPhaseIterator is LESS costly than exclTwoPhaseIterator, check it first
return new TwoPhaseIterator(reqApproximation) {
@Override
public boolean matches() throws IOException {
final int doc = reqApproximation.docID();
// check if the doc is not excluded
int exclDoc = exclApproximation.docID();
if (exclDoc < doc) {
exclDoc = exclApproximation.advance(doc);
}
if (exclDoc != doc) {
return matchesOrNull(reqTwoPhaseIterator);
}
return matchesOrNull(reqTwoPhaseIterator) && !matchesOrNull(exclTwoPhaseIterator);
}
@Override
public float matchCost() {
return matchCost;
}
};
} else {
// reqTwoPhaseIterator is MORE costly than exclTwoPhaseIterator, check it last
return new TwoPhaseIterator(reqApproximation) {
@Override
public boolean matches() throws IOException {
final int doc = reqApproximation.docID();
// check if the doc is not excluded
int exclDoc = exclApproximation.docID();
if (exclDoc < doc) {
exclDoc = exclApproximation.advance(doc);
}
if (exclDoc != doc) {
return matchesOrNull(reqTwoPhaseIterator);
}
return !matchesOrNull(exclTwoPhaseIterator) && matchesOrNull(reqTwoPhaseIterator);
}
@Override
public float matchCost() {
return matchCost;
}
};
}
}
}