blob: e24e62b2658609e5369e9832fc409daddc792be3 [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 org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.FilteredTermsEnum;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.AttributeSource;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
public class TestMultiTermQueryRewrites extends LuceneTestCase {
static Directory dir, sdir1, sdir2;
static IndexReader reader, multiReader, multiReaderDupls;
static IndexSearcher searcher, multiSearcher, multiSearcherDupls;
@BeforeClass
public static void beforeClass() throws Exception {
dir = newDirectory();
sdir1 = newDirectory();
sdir2 = newDirectory();
final RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new MockAnalyzer(random()));
final RandomIndexWriter swriter1 = new RandomIndexWriter(random(), sdir1, new MockAnalyzer(random()));
final RandomIndexWriter swriter2 = new RandomIndexWriter(random(), sdir2, new MockAnalyzer(random()));
for (int i = 0; i < 10; i++) {
Document doc = new Document();
doc.add(newStringField("data", Integer.toString(i), Field.Store.NO));
writer.addDocument(doc);
((i % 2 == 0) ? swriter1 : swriter2).addDocument(doc);
}
writer.forceMerge(1); swriter1.forceMerge(1); swriter2.forceMerge(1);
writer.close(); swriter1.close(); swriter2.close();
reader = DirectoryReader.open(dir);
searcher = newSearcher(reader);
multiReader = new MultiReader(new IndexReader[] {
DirectoryReader.open(sdir1), DirectoryReader.open(sdir2)
}, true);
multiSearcher = newSearcher(multiReader);
multiReaderDupls = new MultiReader(new IndexReader[] {
DirectoryReader.open(sdir1), DirectoryReader.open(dir)
}, true);
multiSearcherDupls = newSearcher(multiReaderDupls);
}
@AfterClass
public static void afterClass() throws Exception {
reader.close();
multiReader.close();
multiReaderDupls.close();
dir.close(); sdir1.close(); sdir2.close();
reader = multiReader = multiReaderDupls = null;
searcher = multiSearcher = multiSearcherDupls = null;
dir = sdir1 = sdir2 = null;
}
private Query extractInnerQuery(Query q) {
if (q instanceof ConstantScoreQuery) {
// wrapped as ConstantScoreQuery
q = ((ConstantScoreQuery) q).getQuery();
}
return q;
}
private Term extractTerm(Query q) {
q = extractInnerQuery(q);
return ((TermQuery) q).getTerm();
}
private void checkBooleanQueryOrder(Query q) {
q = extractInnerQuery(q);
final BooleanQuery bq = (BooleanQuery) q;
Term last = null, act;
for (BooleanClause clause : bq.clauses()) {
act = extractTerm(clause.getQuery());
if (last != null) {
assertTrue("sort order of terms in BQ violated", last.compareTo(act) < 0);
}
last = act;
}
}
private void checkDuplicateTerms(MultiTermQuery.RewriteMethod method) throws Exception {
final MultiTermQuery mtq = TermRangeQuery.newStringRange("data", "2", "7", true, true);
mtq.setRewriteMethod(method);
final Query q1 = searcher.rewrite(mtq);
final Query q2 = multiSearcher.rewrite(mtq);
final Query q3 = multiSearcherDupls.rewrite(mtq);
if (VERBOSE) {
System.out.println();
System.out.println("single segment: " + q1);
System.out.println("multi segment: " + q2);
System.out.println("multi segment with duplicates: " + q3);
}
assertEquals("The multi-segment case must produce same rewritten query", q1, q2);
assertEquals("The multi-segment case with duplicates must produce same rewritten query", q1, q3);
checkBooleanQueryOrder(q1);
checkBooleanQueryOrder(q2);
checkBooleanQueryOrder(q3);
}
public void testRewritesWithDuplicateTerms() throws Exception {
checkDuplicateTerms(MultiTermQuery.SCORING_BOOLEAN_REWRITE);
checkDuplicateTerms(MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE);
// use a large PQ here to only test duplicate terms and dont mix up when all scores are equal
checkDuplicateTerms(new MultiTermQuery.TopTermsScoringBooleanQueryRewrite(1024));
checkDuplicateTerms(new MultiTermQuery.TopTermsBoostOnlyBooleanQueryRewrite(1024));
}
private void checkBooleanQueryBoosts(BooleanQuery bq) {
for (BooleanClause clause : bq.clauses()) {
final BoostQuery boostQ = (BoostQuery) clause.getQuery();
final TermQuery mtq = (TermQuery) boostQ.getQuery();
assertEquals("Parallel sorting of boosts in rewrite mode broken",
Float.parseFloat(mtq.getTerm().text()), boostQ.getBoost(), 0);
}
}
private void checkBoosts(MultiTermQuery.RewriteMethod method) throws Exception {
final MultiTermQuery mtq = new MultiTermQuery("data") {
@Override
protected TermsEnum getTermsEnum(Terms terms, AttributeSource atts) throws IOException {
return new FilteredTermsEnum(terms.iterator()) {
final BoostAttribute boostAtt =
attributes().addAttribute(BoostAttribute.class);
@Override
protected AcceptStatus accept(BytesRef term) {
boostAtt.setBoost(Float.parseFloat(term.utf8ToString()));
if (term.length == 0) {
return AcceptStatus.NO;
}
char c = (char) (term.bytes[term.offset] & 0xff);
if (c >= '2') {
if (c <= '7') {
return AcceptStatus.YES;
} else {
return AcceptStatus.END;
}
} else {
return AcceptStatus.NO;
}
}
};
}
@Override
public String toString(String field) {
return "dummy";
}
@Override
public void visit(QueryVisitor visitor) {
}
};
mtq.setRewriteMethod(method);
final Query q1 = searcher.rewrite(mtq);
final Query q2 = multiSearcher.rewrite(mtq);
final Query q3 = multiSearcherDupls.rewrite(mtq);
if (VERBOSE) {
System.out.println();
System.out.println("single segment: " + q1);
System.out.println("multi segment: " + q2);
System.out.println("multi segment with duplicates: " + q3);
}
assertEquals("The multi-segment case must produce same rewritten query", q1, q2);
assertEquals("The multi-segment case with duplicates must produce same rewritten query", q1, q3);
if (q1 instanceof MatchNoDocsQuery) {
assertTrue(q1 instanceof MatchNoDocsQuery);
assertTrue(q3 instanceof MatchNoDocsQuery);
} else {
checkBooleanQueryBoosts((BooleanQuery) q1);
checkBooleanQueryBoosts((BooleanQuery) q2);
checkBooleanQueryBoosts((BooleanQuery) q3);
assert false;
}
}
public void testBoosts() throws Exception {
checkBoosts(MultiTermQuery.SCORING_BOOLEAN_REWRITE);
// use a large PQ here to only test boosts and dont mix up when all scores are equal
checkBoosts(new MultiTermQuery.TopTermsScoringBooleanQueryRewrite(1024));
}
private void checkMaxClauseLimitation(MultiTermQuery.RewriteMethod method) throws Exception {
int savedMaxClauseCount = BooleanQuery.getMaxClauseCount();
BooleanQuery.setMaxClauseCount(3);
final MultiTermQuery mtq = TermRangeQuery.newStringRange("data", "2", "7", true, true);
mtq.setRewriteMethod(method);
try {
BooleanQuery.TooManyClauses expected = expectThrows(BooleanQuery.TooManyClauses.class, () -> {
multiSearcherDupls.rewrite(mtq);
});
// Maybe remove this assert in later versions, when internal API changes:
assertEquals("Should throw BooleanQuery.TooManyClauses with a stacktrace containing checkMaxClauseCount()",
"checkMaxClauseCount", expected.getStackTrace()[0].getMethodName());
} finally {
BooleanQuery.setMaxClauseCount(savedMaxClauseCount);
}
}
private void checkNoMaxClauseLimitation(MultiTermQuery.RewriteMethod method) throws Exception {
int savedMaxClauseCount = BooleanQuery.getMaxClauseCount();
BooleanQuery.setMaxClauseCount(3);
final MultiTermQuery mtq = TermRangeQuery.newStringRange("data", "2", "7", true, true);
mtq.setRewriteMethod(method);
try {
multiSearcherDupls.rewrite(mtq);
} finally {
BooleanQuery.setMaxClauseCount(savedMaxClauseCount);
}
}
public void testMaxClauseLimitations() throws Exception {
checkMaxClauseLimitation(MultiTermQuery.SCORING_BOOLEAN_REWRITE);
checkMaxClauseLimitation(MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE);
checkNoMaxClauseLimitation(MultiTermQuery.CONSTANT_SCORE_REWRITE);
checkNoMaxClauseLimitation(new MultiTermQuery.TopTermsScoringBooleanQueryRewrite(1024));
checkNoMaxClauseLimitation(new MultiTermQuery.TopTermsBoostOnlyBooleanQueryRewrite(1024));
}
}