| Index: lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java (working copy) |
| @@ -19,6 +19,7 @@ |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| +import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| @@ -341,6 +342,11 @@ |
| @Override |
| public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) |
| throws IOException { |
| + // initially the user provided value, |
| + // but if minNrShouldMatch == optional.size(), |
| + // we will optimize and move these to required, making this 0 |
| + int minShouldMatch = minNrShouldMatch; |
| + |
| List<Scorer> required = new ArrayList<>(); |
| List<Scorer> prohibited = new ArrayList<>(); |
| List<Scorer> optional = new ArrayList<>(); |
| @@ -360,11 +366,20 @@ |
| optional.add(subScorer); |
| } |
| } |
| - |
| - if (required.size() == 0 && optional.size() == 0) { |
| + |
| + // scorer simplifications: |
| + |
| + if (optional.size() == minShouldMatch) { |
| + // any optional clauses are in fact required |
| + required.addAll(optional); |
| + optional.clear(); |
| + minShouldMatch = 0; |
| + } |
| + |
| + if (required.isEmpty() && optional.isEmpty()) { |
| // no required and optional clauses. |
| return null; |
| - } else if (optional.size() < minNrShouldMatch) { |
| + } else if (optional.size() < minShouldMatch) { |
| // either >1 req scorer, or there are 0 req scorers and at least 1 |
| // optional scorer. Therefore if there are not enough optional scorers |
| // no documents will be matched by the query |
| @@ -371,23 +386,49 @@ |
| return null; |
| } |
| |
| - // simple conjunction |
| - if (optional.size() == 0 && prohibited.size() == 0) { |
| - float coord = disableCoord ? 1.0f : coord(required.size(), maxCoord); |
| - return new ConjunctionScorer(this, required.toArray(new Scorer[required.size()]), coord); |
| + // three cases: conjunction, disjunction, or mix |
| + |
| + // pure conjunction |
| + if (optional.isEmpty()) { |
| + return excl(req(required, disableCoord), prohibited); |
| } |
| |
| - // simple disjunction |
| - if (required.size() == 0 && prohibited.size() == 0 && minNrShouldMatch <= 1 && optional.size() > 1) { |
| - float coord[] = new float[optional.size()+1]; |
| - for (int i = 0; i < coord.length; i++) { |
| - coord[i] = disableCoord ? 1.0f : coord(i, maxCoord); |
| + // pure disjunction |
| + if (required.isEmpty()) { |
| + return excl(opt(optional, minShouldMatch, disableCoord), prohibited); |
| + } |
| + |
| + // conjunction-disjunction mix: |
| + // we create the required and optional pieces with coord disabled, and then |
| + // combine the two: if minNrShouldMatch > 0, then its a conjunction: because the |
| + // optional side must match. otherwise its required + optional, factoring the |
| + // number of optional terms into the coord calculation |
| + |
| + Scorer req = excl(req(required, true), prohibited); |
| + Scorer opt = opt(optional, minShouldMatch, true); |
| + |
| + // TODO: clean this up: its horrible |
| + if (disableCoord) { |
| + if (minShouldMatch > 0) { |
| + return new ConjunctionScorer(this, new Scorer[] { req, opt }, 1F); |
| + } else { |
| + return new ReqOptSumScorer(req, opt); |
| } |
| - return new DisjunctionSumScorer(this, optional.toArray(new Scorer[optional.size()]), coord); |
| + } else if (optional.size() == 1) { |
| + if (minShouldMatch > 0) { |
| + return new ConjunctionScorer(this, new Scorer[] { req, opt }, coord(required.size()+1, maxCoord)); |
| + } else { |
| + float coordReq = coord(required.size(), maxCoord); |
| + float coordBoth = coord(required.size() + 1, maxCoord); |
| + return new BooleanTopLevelScorers.ReqSingleOptScorer(req, opt, coordReq, coordBoth); |
| + } |
| + } else { |
| + if (minShouldMatch > 0) { |
| + return new BooleanTopLevelScorers.CoordinatingConjunctionScorer(this, coords(), req, required.size(), opt); |
| + } else { |
| + return new BooleanTopLevelScorers.ReqMultiOptScorer(req, opt, required.size(), coords()); |
| + } |
| } |
| - |
| - // Return a BooleanScorer2 |
| - return new BooleanScorer2(this, disableCoord, minNrShouldMatch, required, prohibited, optional, maxCoord); |
| } |
| |
| @Override |
| @@ -396,17 +437,89 @@ |
| // BS2 (in-order) will be used by scorer() |
| return false; |
| } |
| + int optionalCount = 0; |
| for (BooleanClause c : clauses) { |
| if (c.isRequired()) { |
| // BS2 (in-order) will be used by scorer() |
| return false; |
| + } else if (!c.isProhibited()) { |
| + optionalCount++; |
| } |
| } |
| |
| + if (optionalCount == minNrShouldMatch) { |
| + return false; // BS2 (in-order) will be used, as this means conjunction |
| + } |
| + |
| // scorer() will return an out-of-order scorer if requested. |
| return true; |
| } |
| |
| + private Scorer req(List<Scorer> required, boolean disableCoord) { |
| + if (required.size() == 1) { |
| + Scorer req = required.get(0); |
| + if (!disableCoord && maxCoord > 1) { |
| + return new BooleanTopLevelScorers.BoostedScorer(req, coord(1, maxCoord)); |
| + } else { |
| + return req; |
| + } |
| + } else { |
| + return new ConjunctionScorer(this, |
| + required.toArray(new Scorer[required.size()]), |
| + disableCoord ? 1.0F : coord(required.size(), maxCoord)); |
| + } |
| + } |
| + |
| + private Scorer excl(Scorer main, List<Scorer> prohibited) throws IOException { |
| + if (prohibited.isEmpty()) { |
| + return main; |
| + } else if (prohibited.size() == 1) { |
| + return new ReqExclScorer(main, prohibited.get(0)); |
| + } else { |
| + float coords[] = new float[prohibited.size()+1]; |
| + Arrays.fill(coords, 1F); |
| + // TODO: don't score here. |
| + return new ReqExclScorer(main, |
| + new DisjunctionSumScorer(this, |
| + prohibited.toArray(new Scorer[prohibited.size()]), |
| + coords)); |
| + } |
| + } |
| + |
| + private Scorer opt(List<Scorer> optional, int minShouldMatch, boolean disableCoord) throws IOException { |
| + if (optional.size() == 1) { |
| + Scorer opt = optional.get(0); |
| + if (!disableCoord && maxCoord > 1) { |
| + return new BooleanTopLevelScorers.BoostedScorer(opt, coord(1, maxCoord)); |
| + } else { |
| + return opt; |
| + } |
| + } else { |
| + float coords[]; |
| + if (disableCoord) { |
| + coords = new float[optional.size()+1]; |
| + Arrays.fill(coords, 1F); |
| + } else { |
| + coords = coords(); |
| + } |
| + if (minShouldMatch > 1) { |
| + return new MinShouldMatchSumScorer(this, optional, minShouldMatch, coords); |
| + } else { |
| + return new DisjunctionSumScorer(this, |
| + optional.toArray(new Scorer[optional.size()]), |
| + coords); |
| + } |
| + } |
| + } |
| + |
| + private float[] coords() { |
| + float[] coords = new float[maxCoord+1]; |
| + coords[0] = 0F; |
| + for (int i = 1; i < coords.length; i++) { |
| + coords[i] = coord(i, maxCoord); |
| + } |
| + return coords; |
| + } |
| } |
| |
| @Override |
| Index: lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java (working copy) |
| @@ -1,328 +0,0 @@ |
| -package org.apache.lucene.search; |
| - |
| -/* |
| - * 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. |
| - */ |
| - |
| -import java.io.IOException; |
| -import java.util.ArrayList; |
| -import java.util.Collection; |
| -import java.util.List; |
| - |
| -import org.apache.lucene.search.BooleanClause.Occur; |
| -import org.apache.lucene.search.BooleanQuery.BooleanWeight; |
| -import org.apache.lucene.search.similarities.Similarity; |
| - |
| -/* See the description in BooleanScorer.java, comparing |
| - * BooleanScorer & BooleanScorer2 */ |
| - |
| -/** An alternative to BooleanScorer that also allows a minimum number |
| - * of optional scorers that should match. |
| - * <br>Implements skipTo(), and has no limitations on the numbers of added scorers. |
| - * <br>Uses ConjunctionScorer, DisjunctionScorer, ReqOptScorer and ReqExclScorer. |
| - */ |
| -class BooleanScorer2 extends Scorer { |
| - |
| - private final List<Scorer> requiredScorers; |
| - private final List<Scorer> optionalScorers; |
| - private final List<Scorer> prohibitedScorers; |
| - |
| - private class Coordinator { |
| - final float coordFactors[]; |
| - |
| - Coordinator(int maxCoord, boolean disableCoord) { |
| - coordFactors = new float[optionalScorers.size() + requiredScorers.size() + 1]; |
| - for (int i = 0; i < coordFactors.length; i++) { |
| - coordFactors[i] = disableCoord ? 1.0f : ((BooleanWeight)weight).coord(i, maxCoord); |
| - } |
| - } |
| - |
| - int nrMatchers; // to be increased by score() of match counting scorers. |
| - } |
| - |
| - private final Coordinator coordinator; |
| - |
| - /** The scorer to which all scoring will be delegated, |
| - * except for computing and using the coordination factor. |
| - */ |
| - private final Scorer countingSumScorer; |
| - |
| - /** The number of optionalScorers that need to match (if there are any) */ |
| - private final int minNrShouldMatch; |
| - |
| - private int doc = -1; |
| - |
| - /** |
| - * Creates a {@link Scorer} with the given similarity and lists of required, |
| - * prohibited and optional scorers. In no required scorers are added, at least |
| - * one of the optional scorers will have to match during the search. |
| - * |
| - * @param weight |
| - * The BooleanWeight to be used. |
| - * @param disableCoord |
| - * If this parameter is true, coordination level matching |
| - * ({@link Similarity#coord(int, int)}) is not used. |
| - * @param minNrShouldMatch |
| - * The minimum number of optional added scorers that should match |
| - * during the search. In case no required scorers are added, at least |
| - * one of the optional scorers will have to match during the search. |
| - * @param required |
| - * the list of required scorers. |
| - * @param prohibited |
| - * the list of prohibited scorers. |
| - * @param optional |
| - * the list of optional scorers. |
| - */ |
| - public BooleanScorer2(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch, |
| - List<Scorer> required, List<Scorer> prohibited, List<Scorer> optional, int maxCoord) throws IOException { |
| - super(weight); |
| - if (minNrShouldMatch < 0) { |
| - throw new IllegalArgumentException("Minimum number of optional scorers should not be negative"); |
| - } |
| - this.minNrShouldMatch = minNrShouldMatch; |
| - |
| - optionalScorers = optional; |
| - requiredScorers = required; |
| - prohibitedScorers = prohibited; |
| - coordinator = new Coordinator(maxCoord, disableCoord); |
| - |
| - countingSumScorer = makeCountingSumScorer(disableCoord); |
| - } |
| - |
| - /** Count a scorer as a single match. */ |
| - private class SingleMatchScorer extends Scorer { |
| - private Scorer scorer; |
| - private int lastScoredDoc = -1; |
| - // Save the score of lastScoredDoc, so that we don't compute it more than |
| - // once in score(). |
| - private float lastDocScore = Float.NaN; |
| - |
| - SingleMatchScorer(Scorer scorer) { |
| - super(scorer.weight); |
| - this.scorer = scorer; |
| - } |
| - |
| - @Override |
| - public float score() throws IOException { |
| - int doc = docID(); |
| - if (doc >= lastScoredDoc) { |
| - if (doc > lastScoredDoc) { |
| - lastDocScore = scorer.score(); |
| - lastScoredDoc = doc; |
| - } |
| - coordinator.nrMatchers++; |
| - } |
| - return lastDocScore; |
| - } |
| - |
| - @Override |
| - public int freq() throws IOException { |
| - return 1; |
| - } |
| - |
| - @Override |
| - public int docID() { |
| - return scorer.docID(); |
| - } |
| - |
| - @Override |
| - public int nextDoc() throws IOException { |
| - return scorer.nextDoc(); |
| - } |
| - |
| - @Override |
| - public int advance(int target) throws IOException { |
| - return scorer.advance(target); |
| - } |
| - |
| - @Override |
| - public long cost() { |
| - return scorer.cost(); |
| - } |
| - } |
| - |
| - private Scorer countingDisjunctionSumScorer(final List<Scorer> scorers, |
| - int minNrShouldMatch) throws IOException { |
| - // each scorer from the list counted as a single matcher |
| - if (minNrShouldMatch > 1) { |
| - return new MinShouldMatchSumScorer(weight, scorers, minNrShouldMatch) { |
| - @Override |
| - public float score() throws IOException { |
| - coordinator.nrMatchers += super.nrMatchers; |
| - return super.score(); |
| - } |
| - }; |
| - } else { |
| - // we pass null for coord[] since we coordinate ourselves and override score() |
| - return new DisjunctionSumScorer(weight, scorers.toArray(new Scorer[scorers.size()]), null) { |
| - @Override |
| - public float score() throws IOException { |
| - coordinator.nrMatchers += super.nrMatchers; |
| - return (float) super.score; |
| - } |
| - }; |
| - } |
| - } |
| - |
| - private Scorer countingConjunctionSumScorer(boolean disableCoord, |
| - List<Scorer> requiredScorers) throws IOException { |
| - // each scorer from the list counted as a single matcher |
| - final int requiredNrMatchers = requiredScorers.size(); |
| - return new ConjunctionScorer(weight, requiredScorers.toArray(new Scorer[requiredScorers.size()])) { |
| - private int lastScoredDoc = -1; |
| - // Save the score of lastScoredDoc, so that we don't compute it more than |
| - // once in score(). |
| - private float lastDocScore = Float.NaN; |
| - @Override public float score() throws IOException { |
| - int doc = docID(); |
| - if (doc >= lastScoredDoc) { |
| - if (doc > lastScoredDoc) { |
| - lastDocScore = super.score(); |
| - lastScoredDoc = doc; |
| - } |
| - coordinator.nrMatchers += requiredNrMatchers; |
| - } |
| - // All scorers match, so defaultSimilarity super.score() always has 1 as |
| - // the coordination factor. |
| - // Therefore the sum of the scores of the requiredScorers |
| - // is used as score. |
| - return lastDocScore; |
| - } |
| - }; |
| - } |
| - |
| - private Scorer dualConjunctionSumScorer(boolean disableCoord, |
| - Scorer req1, Scorer req2) throws IOException { // non counting. |
| - return new ConjunctionScorer(weight, new Scorer[] { req1, req2 }); |
| - // All scorers match, so defaultSimilarity always has 1 as |
| - // the coordination factor. |
| - // Therefore the sum of the scores of two scorers |
| - // is used as score. |
| - } |
| - |
| - /** Returns the scorer to be used for match counting and score summing. |
| - * Uses requiredScorers, optionalScorers and prohibitedScorers. |
| - */ |
| - private Scorer makeCountingSumScorer(boolean disableCoord) throws IOException { // each scorer counted as a single matcher |
| - return (requiredScorers.size() == 0) |
| - ? makeCountingSumScorerNoReq(disableCoord) |
| - : makeCountingSumScorerSomeReq(disableCoord); |
| - } |
| - |
| - private Scorer makeCountingSumScorerNoReq(boolean disableCoord) throws IOException { // No required scorers |
| - // minNrShouldMatch optional scorers are required, but at least 1 |
| - int nrOptRequired = (minNrShouldMatch < 1) ? 1 : minNrShouldMatch; |
| - Scorer requiredCountingSumScorer; |
| - if (optionalScorers.size() > nrOptRequired) |
| - requiredCountingSumScorer = countingDisjunctionSumScorer(optionalScorers, nrOptRequired); |
| - else if (optionalScorers.size() == 1) |
| - requiredCountingSumScorer = new SingleMatchScorer(optionalScorers.get(0)); |
| - else { |
| - requiredCountingSumScorer = countingConjunctionSumScorer(disableCoord, optionalScorers); |
| - } |
| - return addProhibitedScorers(requiredCountingSumScorer); |
| - } |
| - |
| - private Scorer makeCountingSumScorerSomeReq(boolean disableCoord) throws IOException { // At least one required scorer. |
| - if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required. |
| - ArrayList<Scorer> allReq = new ArrayList<>(requiredScorers); |
| - allReq.addAll(optionalScorers); |
| - return addProhibitedScorers(countingConjunctionSumScorer(disableCoord, allReq)); |
| - } else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer |
| - Scorer requiredCountingSumScorer = |
| - requiredScorers.size() == 1 |
| - ? new SingleMatchScorer(requiredScorers.get(0)) |
| - : countingConjunctionSumScorer(disableCoord, requiredScorers); |
| - if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers |
| - return addProhibitedScorers( |
| - dualConjunctionSumScorer( // non counting |
| - disableCoord, |
| - requiredCountingSumScorer, |
| - countingDisjunctionSumScorer( |
| - optionalScorers, |
| - minNrShouldMatch))); |
| - } else { // minNrShouldMatch == 0 |
| - return new ReqOptSumScorer( |
| - addProhibitedScorers(requiredCountingSumScorer), |
| - optionalScorers.size() == 1 |
| - ? new SingleMatchScorer(optionalScorers.get(0)) |
| - // require 1 in combined, optional scorer. |
| - : countingDisjunctionSumScorer(optionalScorers, 1)); |
| - } |
| - } |
| - } |
| - |
| - /** Returns the scorer to be used for match counting and score summing. |
| - * Uses the given required scorer and the prohibitedScorers. |
| - * @param requiredCountingSumScorer A required scorer already built. |
| - */ |
| - private Scorer addProhibitedScorers(Scorer requiredCountingSumScorer) throws IOException |
| - { |
| - return (prohibitedScorers.size() == 0) |
| - ? requiredCountingSumScorer // no prohibited |
| - : new ReqExclScorer(requiredCountingSumScorer, |
| - ((prohibitedScorers.size() == 1) |
| - ? prohibitedScorers.get(0) |
| - : new MinShouldMatchSumScorer(weight, prohibitedScorers))); |
| - } |
| - |
| - @Override |
| - public int docID() { |
| - return doc; |
| - } |
| - |
| - @Override |
| - public int nextDoc() throws IOException { |
| - return doc = countingSumScorer.nextDoc(); |
| - } |
| - |
| - @Override |
| - public float score() throws IOException { |
| - coordinator.nrMatchers = 0; |
| - float sum = countingSumScorer.score(); |
| - return sum * coordinator.coordFactors[coordinator.nrMatchers]; |
| - } |
| - |
| - @Override |
| - public int freq() throws IOException { |
| - return countingSumScorer.freq(); |
| - } |
| - |
| - @Override |
| - public int advance(int target) throws IOException { |
| - return doc = countingSumScorer.advance(target); |
| - } |
| - |
| - @Override |
| - public long cost() { |
| - return countingSumScorer.cost(); |
| - } |
| - |
| - @Override |
| - public Collection<ChildScorer> getChildren() { |
| - ArrayList<ChildScorer> children = new ArrayList<>(); |
| - for (Scorer s : optionalScorers) { |
| - children.add(new ChildScorer(s, "SHOULD")); |
| - } |
| - for (Scorer s : prohibitedScorers) { |
| - children.add(new ChildScorer(s, "MUST_NOT")); |
| - } |
| - for (Scorer s : requiredScorers) { |
| - children.add(new ChildScorer(s, "MUST")); |
| - } |
| - return children; |
| - } |
| -} |
| Index: lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java (revision 0) |
| +++ lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java (working copy) |
| @@ -0,0 +1,136 @@ |
| +package org.apache.lucene.search; |
| + |
| +/* |
| + * 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. |
| + */ |
| + |
| +import java.io.IOException; |
| + |
| +/** Internal document-at-a-time scorers used to deal with stupid coord() computation */ |
| +class BooleanTopLevelScorers { |
| + |
| + /** |
| + * Used when there is more than one scorer in a query, but a segment |
| + * only had one non-null scorer. This just wraps that scorer directly |
| + * to factor in coord(). |
| + */ |
| + static class BoostedScorer extends FilterScorer { |
| + private final float boost; |
| + |
| + BoostedScorer(Scorer in, float boost) { |
| + super(in); |
| + this.boost = boost; |
| + } |
| + |
| + @Override |
| + public float score() throws IOException { |
| + return in.score() * boost; |
| + } |
| + } |
| + |
| + /** |
| + * Used when there are both mandatory and optional clauses, but minShouldMatch |
| + * dictates that some of the optional clauses must match. The query is a conjunction, |
| + * but must compute coord based on how many optional subscorers matched (freq). |
| + */ |
| + static class CoordinatingConjunctionScorer extends ConjunctionScorer { |
| + private final float coords[]; |
| + private final int reqCount; |
| + private final Scorer req; |
| + private final Scorer opt; |
| + |
| + CoordinatingConjunctionScorer(Weight weight, float coords[], Scorer req, int reqCount, Scorer opt) { |
| + super(weight, new Scorer[] { req, opt }); |
| + this.coords = coords; |
| + this.req = req; |
| + this.reqCount = reqCount; |
| + this.opt = opt; |
| + } |
| + |
| + @Override |
| + public float score() throws IOException { |
| + return (req.score() + opt.score()) * coords[reqCount + opt.freq()]; |
| + } |
| + } |
| + |
| + /** |
| + * Used when there are mandatory clauses with one optional clause: we compute |
| + * coord based on whether the optional clause matched or not. |
| + */ |
| + static class ReqSingleOptScorer extends ReqOptSumScorer { |
| + // coord factor if just the required part matches |
| + private final float coordReq; |
| + // coord factor if both required and optional part matches |
| + private final float coordBoth; |
| + |
| + public ReqSingleOptScorer(Scorer reqScorer, Scorer optScorer, float coordReq, float coordBoth) { |
| + super(reqScorer, optScorer); |
| + this.coordReq = coordReq; |
| + this.coordBoth = coordBoth; |
| + } |
| + |
| + @Override |
| + public float score() throws IOException { |
| + int curDoc = reqScorer.docID(); |
| + float reqScore = reqScorer.score(); |
| + if (optScorer == null) { |
| + return reqScore * coordReq; |
| + } |
| + |
| + int optScorerDoc = optScorer.docID(); |
| + if (optScorerDoc < curDoc && (optScorerDoc = optScorer.advance(curDoc)) == NO_MORE_DOCS) { |
| + optScorer = null; |
| + return reqScore * coordReq; |
| + } |
| + |
| + return optScorerDoc == curDoc ? (reqScore + optScorer.score()) * coordBoth : reqScore * coordReq; |
| + } |
| + } |
| + |
| + /** |
| + * Used when there are mandatory clauses with optional clauses: we compute |
| + * coord based on how many optional subscorers matched (freq). |
| + */ |
| + static class ReqMultiOptScorer extends ReqOptSumScorer { |
| + private final int requiredCount; |
| + private final float coords[]; |
| + private final Scorer disjunction; |
| + |
| + public ReqMultiOptScorer(Scorer reqScorer, Scorer optScorer, int requiredCount, float coords[]) { |
| + super(reqScorer, optScorer); |
| + this.requiredCount = requiredCount; |
| + this.coords = coords; |
| + this.disjunction = optScorer; |
| + } |
| + |
| + @Override |
| + public float score() throws IOException { |
| + int curDoc = reqScorer.docID(); |
| + float reqScore = reqScorer.score(); |
| + if (optScorer == null) { |
| + return reqScore * coords[requiredCount]; |
| + } |
| + |
| + int optScorerDoc = optScorer.docID(); |
| + if (optScorerDoc < curDoc && (optScorerDoc = optScorer.advance(curDoc)) == NO_MORE_DOCS) { |
| + optScorer = null; |
| + return reqScore * coords[requiredCount]; |
| + } |
| + |
| + return optScorerDoc == curDoc ? (reqScore + optScorer.score()) * coords[requiredCount + disjunction.freq()] : reqScore * coords[requiredCount]; |
| + } |
| + } |
| +} |
| |
| Property changes on: lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| ## -0,0 +1 ## |
| +native |
| \ No newline at end of property |
| Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java (working copy) |
| @@ -42,7 +42,7 @@ |
| * Organize subScorers into a min heap with scorers generating the earliest document on top. |
| */ |
| protected final void heapify() { |
| - for (int i = (numScorers >> 1) - 1; i >= 0; i--) { |
| + for (int i = (numScorers >>> 1) - 1; i >= 0; i--) { |
| heapAdjust(i); |
| } |
| } |
| @@ -55,7 +55,7 @@ |
| Scorer scorer = subScorers[root]; |
| int doc = scorer.docID(); |
| int i = root; |
| - while (i <= (numScorers >> 1) - 1) { |
| + while (i <= (numScorers >>> 1) - 1) { |
| int lchild = (i << 1) + 1; |
| Scorer lscorer = subScorers[lchild]; |
| int ldoc = lscorer.docID(); |
| Index: lucene/core/src/java/org/apache/lucene/search/FilterScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/FilterScorer.java (revision 0) |
| +++ lucene/core/src/java/org/apache/lucene/search/FilterScorer.java (working copy) |
| @@ -0,0 +1,82 @@ |
| +package org.apache.lucene.search; |
| + |
| +/* |
| + * 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. |
| + */ |
| + |
| +import java.io.IOException; |
| +import java.util.Collection; |
| + |
| +import org.apache.lucene.util.AttributeSource; |
| + |
| +/** |
| + * A {@code FilterScorer} contains another {@code Scorer}, which it |
| + * uses as its basic source of data, possibly transforming the data along the |
| + * way or providing additional functionality. The class |
| + * {@code FilterScorer} itself simply implements all abstract methods |
| + * of {@code Scorer} with versions that pass all requests to the |
| + * contained scorer. Subclasses of {@code FilterScorer} may |
| + * further override some of these methods and may also provide additional |
| + * methods and fields. |
| + */ |
| +public abstract class FilterScorer extends Scorer { |
| + protected final Scorer in; |
| + |
| + public FilterScorer(Scorer in) { |
| + super(in.weight); |
| + this.in = in; |
| + } |
| + |
| + @Override |
| + public float score() throws IOException { |
| + return in.score(); |
| + } |
| + |
| + @Override |
| + public int freq() throws IOException { |
| + return in.freq(); |
| + } |
| + |
| + @Override |
| + public int docID() { |
| + return in.docID(); |
| + } |
| + |
| + @Override |
| + public int nextDoc() throws IOException { |
| + return in.nextDoc(); |
| + } |
| + |
| + @Override |
| + public int advance(int target) throws IOException { |
| + return in.advance(target); |
| + } |
| + |
| + @Override |
| + public long cost() { |
| + return in.cost(); |
| + } |
| + |
| + @Override |
| + public Collection<ChildScorer> getChildren() { |
| + return in.getChildren(); |
| + } |
| + |
| + @Override |
| + public AttributeSource attributes() { |
| + return in.attributes(); |
| + } |
| +} |
| |
| Property changes on: lucene/core/src/java/org/apache/lucene/search/FilterScorer.java |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| ## -0,0 +1 ## |
| +native |
| \ No newline at end of property |
| Index: lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java (working copy) |
| @@ -59,6 +59,8 @@ |
| /** The number of subscorers that provide the current match. */ |
| protected int nrMatchers = -1; |
| private double score = Float.NaN; |
| + |
| + private final float coord[]; |
| |
| /** |
| * Construct a <code>MinShouldMatchSumScorer</code>. |
| @@ -72,7 +74,7 @@ |
| * <br>When minimumNrMatchers equals the number of subScorers, |
| * it is more efficient to use <code>ConjunctionScorer</code>. |
| */ |
| - public MinShouldMatchSumScorer(Weight weight, List<Scorer> subScorers, int minimumNrMatchers) throws IOException { |
| + public MinShouldMatchSumScorer(Weight weight, List<Scorer> subScorers, int minimumNrMatchers, float coord[]) throws IOException { |
| super(weight); |
| this.nrInHeap = this.numScorers = subScorers.size(); |
| |
| @@ -105,17 +107,10 @@ |
| for (int i = 0; i < nrInHeap; i++) { |
| this.subScorers[i] = this.sortedSubScorers[mm-1+i]; |
| } |
| + this.coord = coord; |
| minheapHeapify(); |
| assert minheapCheck(); |
| } |
| - |
| - /** |
| - * Construct a <code>DisjunctionScorer</code>, using one as the minimum number |
| - * of matching subscorers. |
| - */ |
| - public MinShouldMatchSumScorer(Weight weight, List<Scorer> subScorers) throws IOException { |
| - this(weight, subScorers, 1); |
| - } |
| |
| @Override |
| public final Collection<ChildScorer> getChildren() { |
| @@ -223,7 +218,7 @@ |
| */ |
| @Override |
| public float score() throws IOException { |
| - return (float) score; |
| + return coord[nrMatchers] * (float) score; |
| } |
| |
| @Override |
| Index: lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java (working copy) |
| @@ -111,7 +111,7 @@ |
| |
| @Override |
| public Collection<ChildScorer> getChildren() { |
| - return Collections.singleton(new ChildScorer(reqScorer, "FILTERED")); |
| + return Collections.singleton(new ChildScorer(reqScorer, "MUST")); |
| } |
| |
| @Override |
| Index: lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java (working copy) |
| @@ -29,8 +29,8 @@ |
| /** The scorers passed from the constructor. |
| * These are set to null as soon as their next() or skipTo() returns false. |
| */ |
| - private Scorer reqScorer; |
| - private Scorer optScorer; |
| + protected Scorer reqScorer; |
| + protected Scorer optScorer; |
| |
| /** Construct a <code>ReqOptScorer</code>. |
| * @param reqScorer The required scorer. This must match. |
| Index: lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java |
| =================================================================== |
| --- lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java (revision 1596608) |
| +++ lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java (working copy) |
| @@ -32,23 +32,21 @@ |
| * several places, however all they have in hand is a {@link Scorer} object, and |
| * might end up computing the score of a document more than once. |
| */ |
| -public class ScoreCachingWrappingScorer extends Scorer { |
| +public class ScoreCachingWrappingScorer extends FilterScorer { |
| |
| - private final Scorer scorer; |
| private int curDoc = -1; |
| private float curScore; |
| |
| /** Creates a new instance by wrapping the given scorer. */ |
| public ScoreCachingWrappingScorer(Scorer scorer) { |
| - super(scorer.weight); |
| - this.scorer = scorer; |
| + super(scorer); |
| } |
| |
| @Override |
| public float score() throws IOException { |
| - int doc = scorer.docID(); |
| + int doc = in.docID(); |
| if (doc != curDoc) { |
| - curScore = scorer.score(); |
| + curScore = in.score(); |
| curDoc = doc; |
| } |
| |
| @@ -56,32 +54,7 @@ |
| } |
| |
| @Override |
| - public int freq() throws IOException { |
| - return scorer.freq(); |
| - } |
| - |
| - @Override |
| - public int docID() { |
| - return scorer.docID(); |
| - } |
| - |
| - @Override |
| - public int nextDoc() throws IOException { |
| - return scorer.nextDoc(); |
| - } |
| - |
| - @Override |
| - public int advance(int target) throws IOException { |
| - return scorer.advance(target); |
| - } |
| - |
| - @Override |
| public Collection<ChildScorer> getChildren() { |
| - return Collections.singleton(new ChildScorer(scorer, "CACHED")); |
| + return Collections.singleton(new ChildScorer(in, "CACHED")); |
| } |
| - |
| - @Override |
| - public long cost() { |
| - return scorer.cost(); |
| - } |
| } |
| Index: lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java |
| =================================================================== |
| --- lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java (revision 1596608) |
| +++ lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java (working copy) |
| @@ -171,7 +171,8 @@ |
| boolean includeOptional = occur.contains("SHOULD"); |
| for (int i = 0; i < maxDocs; i++) { |
| Map<Query, Float> doc0 = c.docCounts.get(i); |
| - assertEquals(includeOptional ? 5 : 4, doc0.size()); |
| + // Y doesnt exist in the index, so its not in the scorer tree |
| + assertEquals(4, doc0.size()); |
| assertEquals(1.0F, doc0.get(aQuery), FLOAT_TOLERANCE); |
| assertEquals(4.0F, doc0.get(dQuery), FLOAT_TOLERANCE); |
| if (includeOptional) { |
| @@ -179,7 +180,8 @@ |
| } |
| |
| Map<Query, Float> doc1 = c.docCounts.get(++i); |
| - assertEquals(includeOptional ? 5 : 4, doc1.size()); |
| + // Y doesnt exist in the index, so its not in the scorer tree |
| + assertEquals(4, doc1.size()); |
| assertEquals(1.0F, doc1.get(aQuery), FLOAT_TOLERANCE); |
| assertEquals(1.0F, doc1.get(dQuery), FLOAT_TOLERANCE); |
| if (includeOptional) { |