blob: e29e64ab89f87c5aca4a3e6ffd3975a428565ed0 [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.Arrays;
import java.util.Set;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.Weight.DefaultBulkScorer;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.LuceneTestCase;
public class TestBooleanScorer extends LuceneTestCase {
private static final String FIELD = "category";
public void testMethod() throws Exception {
Directory directory = newDirectory();
String[] values = new String[] { "1", "2", "3", "4" };
RandomIndexWriter writer = new RandomIndexWriter(random(), directory);
for (int i = 0; i < values.length; i++) {
Document doc = new Document();
doc.add(newStringField(FIELD, values[i], Field.Store.YES));
writer.addDocument(doc);
}
IndexReader ir = writer.getReader();
writer.close();
BooleanQuery.Builder booleanQuery1 = new BooleanQuery.Builder();
booleanQuery1.add(new TermQuery(new Term(FIELD, "1")), BooleanClause.Occur.SHOULD);
booleanQuery1.add(new TermQuery(new Term(FIELD, "2")), BooleanClause.Occur.SHOULD);
BooleanQuery.Builder query = new BooleanQuery.Builder();
query.add(booleanQuery1.build(), BooleanClause.Occur.MUST);
query.add(new TermQuery(new Term(FIELD, "9")), BooleanClause.Occur.MUST_NOT);
IndexSearcher indexSearcher = newSearcher(ir);
ScoreDoc[] hits = indexSearcher.search(query.build(), 1000).scoreDocs;
assertEquals("Number of matched documents", 2, hits.length);
ir.close();
directory.close();
}
/** Throws UOE if Weight.scorer is called */
private static class CrazyMustUseBulkScorerQuery extends Query {
@Override
public String toString(String field) {
return "MustUseBulkScorerQuery";
}
@Override
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
return new Weight(CrazyMustUseBulkScorerQuery.this) {
@Override
public void extractTerms(Set<Term> terms) {
throw new UnsupportedOperationException();
}
@Override
public Explanation explain(LeafReaderContext context, int doc) {
throw new UnsupportedOperationException();
}
@Override
public Scorer scorer(LeafReaderContext context) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return false;
}
@Override
public BulkScorer bulkScorer(LeafReaderContext context) {
return new BulkScorer() {
@Override
public int score(LeafCollector collector, Bits acceptDocs, int min, int max) throws IOException {
assert min == 0;
collector.setScorer(new ScoreAndDoc());
collector.collect(0);
return DocIdSetIterator.NO_MORE_DOCS;
}
@Override
public long cost() {
return 1;
}
};
}
};
}
@Override
public void visit(QueryVisitor visitor) {
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
}
/** Make sure BooleanScorer can embed another
* BooleanScorer. */
public void testEmbeddedBooleanScorer() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
doc.add(newTextField("field", "doctors are people who prescribe medicines of which they know little, to cure diseases of which they know less, in human beings of whom they know nothing", Field.Store.NO));
w.addDocument(doc);
IndexReader r = w.getReader();
w.close();
IndexSearcher s = new IndexSearcher(r);
BooleanQuery.Builder q1 = new BooleanQuery.Builder();
q1.add(new TermQuery(new Term("field", "little")), BooleanClause.Occur.SHOULD);
q1.add(new TermQuery(new Term("field", "diseases")), BooleanClause.Occur.SHOULD);
BooleanQuery.Builder q2 = new BooleanQuery.Builder();
q2.add(q1.build(), BooleanClause.Occur.SHOULD);
q2.add(new CrazyMustUseBulkScorerQuery(), BooleanClause.Occur.SHOULD);
assertEquals(1, s.count(q2.build()));
r.close();
dir.close();
}
public void testOptimizeTopLevelClauseOrNull() throws IOException {
// When there is a single non-null scorer, this scorer should be used
// directly
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
doc.add(new StringField("foo", "bar", Store.NO));
w.addDocument(doc);
IndexReader reader = w.getReader();
IndexSearcher searcher = new IndexSearcher(reader);
searcher.setQueryCache(null); // so that weights are not wrapped
final LeafReaderContext ctx = reader.leaves().get(0);
Query query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "bar")), Occur.SHOULD) // existing term
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD) // missing term
.build();
// no scores -> term scorer
Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE_NO_SCORES, 1);
BulkScorer scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof DefaultBulkScorer); // term scorer
// scores -> term scorer too
query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "bar")), Occur.SHOULD) // existing term
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD) // missing term
.build();
weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE, 1);
scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof DefaultBulkScorer); // term scorer
w.close();
reader.close();
dir.close();
}
public void testOptimizeProhibitedClauses() throws IOException {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
doc.add(new StringField("foo", "bar", Store.NO));
doc.add(new StringField("foo", "baz", Store.NO));
w.addDocument(doc);
doc = new Document();
doc.add(new StringField("foo", "baz", Store.NO));
w.addDocument(doc);
w.forceMerge(1);
IndexReader reader = w.getReader();
IndexSearcher searcher = new IndexSearcher(reader);
searcher.setQueryCache(null); // so that weights are not wrapped
final LeafReaderContext ctx = reader.leaves().get(0);
Query query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD)
.add(new TermQuery(new Term("foo", "bar")), Occur.MUST_NOT)
.build();
Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE, 1);
BulkScorer scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof ReqExclBulkScorer);
query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD)
.add(new MatchAllDocsQuery(), Occur.SHOULD)
.add(new TermQuery(new Term("foo", "bar")), Occur.MUST_NOT)
.build();
weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE, 1);
scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof ReqExclBulkScorer);
query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.MUST)
.add(new TermQuery(new Term("foo", "bar")), Occur.MUST_NOT)
.build();
weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE, 1);
scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof ReqExclBulkScorer);
query = new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "bar")), Occur.MUST_NOT)
.build();
weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE, 1);
scorer = ((BooleanWeight) weight).booleanScorer(ctx);
assertTrue(scorer instanceof ReqExclBulkScorer);
w.close();
reader.close();
dir.close();
}
public void testSparseClauseOptimization() throws IOException {
// When some windows have only one scorer that can match, the scorer will
// directly call the collector in this window
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document emptyDoc = new Document();
final int numDocs = atLeast(10);
int numEmptyDocs = atLeast(200);
for (int d = 0; d < numDocs; ++d) {
for (int i = numEmptyDocs; i >= 0; --i) {
w.addDocument(emptyDoc);
}
Document doc = new Document();
for (String value : Arrays.asList("foo", "bar", "baz")) {
if (random().nextBoolean()) {
doc.add(new StringField("field", value, Store.NO));
}
}
}
numEmptyDocs = atLeast(200);
for (int i = numEmptyDocs; i >= 0; --i) {
w.addDocument(emptyDoc);
}
if (random().nextBoolean()) {
w.forceMerge(1);
}
IndexReader reader = w.getReader();
IndexSearcher searcher = newSearcher(reader);
Query query = new BooleanQuery.Builder()
.add(new BoostQuery(new TermQuery(new Term("field", "foo")), 3), Occur.SHOULD)
.add(new BoostQuery(new TermQuery(new Term("field", "bar")), 3), Occur.SHOULD)
.add(new BoostQuery(new TermQuery(new Term("field", "baz")), 3), Occur.SHOULD)
.build();
// duel BS1 vs. BS2
QueryUtils.check(random(), query, searcher);
reader.close();
w.close();
dir.close();
}
public void testFilterConstantScore() throws Exception {
Directory dir = newDirectory();
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
Document doc = new Document();
doc.add(new StringField("foo", "bar", Store.NO));
doc.add(new StringField("foo", "bat", Store.NO));
doc.add(new StringField("foo", "baz", Store.NO));
w.addDocument(doc);
IndexReader reader = w.getReader();
IndexSearcher searcher = new IndexSearcher(reader);
searcher.setQueryCache(null);
{
// single filter rewrites to a constant score query
Query query = new BooleanQuery.Builder().add(new TermQuery(new Term("foo", "bar")), Occur.FILTER).build();
Query rewrite = searcher.rewrite(query);
assertTrue(rewrite instanceof BoostQuery);
assertTrue(((BoostQuery) rewrite).getQuery() instanceof ConstantScoreQuery);
}
Query[] queries = new Query[] {
new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "bar")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "baz")), Occur.FILTER)
.build(),
new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.FILTER)
// non-existing term
.add(new TermQuery(new Term("foo", "arf")), Occur.SHOULD)
.build(),
new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "baz")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "baz")), Occur.FILTER)
// non-existing term
.add(new TermQuery(new Term("foo", "arf")), Occur.SHOULD)
.add(new TermQuery(new Term("foo", "arw")), Occur.SHOULD)
.build()
};
for (Query query : queries) {
Query rewrite = searcher.rewrite(query);
for (ScoreMode scoreMode : ScoreMode.values()) {
Weight weight = searcher.createWeight(rewrite, scoreMode, 1f);
Scorer scorer = weight.scorer(reader.leaves().get(0));
if (scoreMode == ScoreMode.TOP_SCORES) {
assertTrue(scorer instanceof ConstantScoreScorer);
} else {
assertFalse(scorer instanceof ConstantScoreScorer);
}
}
}
queries = new Query[]{
new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "bar")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD)
.build(),
new BooleanQuery.Builder()
.add(new TermQuery(new Term("foo", "bar")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "baz")), Occur.MUST)
// non-existing term
.add(new TermQuery(new Term("foo", "arf")), Occur.SHOULD)
.build(),
new BooleanQuery.Builder()
// non-existing term
.add(new TermQuery(new Term("foo", "bar")), Occur.FILTER)
.add(new TermQuery(new Term("foo", "baz")), Occur.SHOULD)
// non-existing term
.add(new TermQuery(new Term("foo", "arf")), Occur.MUST)
.build()
};
for (Query query : queries) {
Query rewrite = searcher.rewrite(query);
for (ScoreMode scoreMode : ScoreMode.values()) {
Weight weight = searcher.createWeight(rewrite, scoreMode, 1f);
Scorer scorer = weight.scorer(reader.leaves().get(0));
assertFalse(scorer instanceof ConstantScoreScorer);
}
}
reader.close();
w.close();
dir.close();
}
}