package org.apache.lucene.queryparser.classic;
/*
* 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.io.Reader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser.Operator;
import org.apache.lucene.queryparser.flexible.standard.CommonQueryParserConfiguration;
import org.apache.lucene.queryparser.util.QueryParserTestBase;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MultiPhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
/**
* Tests QueryParser.
*/
public class TestQueryParser extends QueryParserTestBase {
public static class QPTestParser extends QueryParser {
public QPTestParser(String f, Analyzer a) {
super(TEST_VERSION_CURRENT, f, a);
}
@Override
protected Query getFuzzyQuery(String field, String termStr,
float minSimilarity) throws ParseException {
throw new ParseException("Fuzzy queries not allowed");
}
@Override
protected Query getWildcardQuery(String field, String termStr)
throws ParseException {
throw new ParseException("Wildcard queries not allowed");
}
}
public QueryParser getParser(Analyzer a) throws Exception {
if (a == null) a = new MockAnalyzer(random(), MockTokenizer.SIMPLE, true);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, getDefaultField(), a);
qp.setDefaultOperator(QueryParserBase.OR_OPERATOR);
return qp;
}
@Override
public CommonQueryParserConfiguration getParserConfig(Analyzer a)
throws Exception {
return getParser(a);
}
@Override
public Query getQuery(String query, CommonQueryParserConfiguration cqpC)
throws Exception {
assert cqpC != null : "Parameter must not be null";
assert (cqpC instanceof QueryParser) : "Parameter must be instance of QueryParser";
QueryParser qp = (QueryParser) cqpC;
return qp.parse(query);
}
@Override
public Query getQuery(String query, Analyzer a) throws Exception {
return getParser(a).parse(query);
}
@Override
public boolean isQueryParserException(Exception exception) {
return exception instanceof ParseException;
}
@Override
public void setDefaultOperatorOR(CommonQueryParserConfiguration cqpC) {
assert (cqpC instanceof QueryParser);
QueryParser qp = (QueryParser) cqpC;
qp.setDefaultOperator(Operator.OR);
}
@Override
public void setDefaultOperatorAND(CommonQueryParserConfiguration cqpC) {
assert (cqpC instanceof QueryParser);
QueryParser qp = (QueryParser) cqpC;
qp.setDefaultOperator(Operator.AND);
}
@Override
public void setAnalyzeRangeTerms(CommonQueryParserConfiguration cqpC,
boolean value) {
assert (cqpC instanceof QueryParser);
QueryParser qp = (QueryParser) cqpC;
qp.setAnalyzeRangeTerms(value);
}
@Override
public void setAutoGeneratePhraseQueries(CommonQueryParserConfiguration cqpC,
boolean value) {
assert (cqpC instanceof QueryParser);
QueryParser qp = (QueryParser) cqpC;
qp.setAutoGeneratePhraseQueries(value);
}
@Override
public void setDateResolution(CommonQueryParserConfiguration cqpC,
CharSequence field, Resolution value) {
assert (cqpC instanceof QueryParser);
QueryParser qp = (QueryParser) cqpC;
qp.setDateResolution(field.toString(), value);
}
@Override
public void testDefaultOperator() throws Exception {
QueryParser qp = getParser(new MockAnalyzer(random()));
// make sure OR is the default:
assertEquals(QueryParserBase.OR_OPERATOR, qp.getDefaultOperator());
setDefaultOperatorAND(qp);
assertEquals(QueryParserBase.AND_OPERATOR, qp.getDefaultOperator());
setDefaultOperatorOR(qp);
assertEquals(QueryParserBase.OR_OPERATOR, qp.getDefaultOperator());
}
// LUCENE-2002: when we run javacc to regen QueryParser,
// we also run a replaceregexp step to fix 2 of the public
// ctors (change them to protected):
//
// protected QueryParser(CharStream stream)
//
// protected QueryParser(QueryParserTokenManager tm)
//
// This test is here as a safety, in case that ant step
// doesn't work for some reason.
@SuppressWarnings("rawtype")
public void testProtectedCtors() throws Exception {
try {
QueryParser.class.getConstructor(new Class[] {CharStream.class});
fail("please switch public QueryParser(CharStream) to be protected");
} catch (NoSuchMethodException nsme) {
// expected
}
try {
QueryParser.class
.getConstructor(new Class[] {QueryParserTokenManager.class});
fail("please switch public QueryParser(QueryParserTokenManager) to be protected");
} catch (NoSuchMethodException nsme) {
// expected
}
}
public void testFuzzySlopeExtendability() throws ParseException {
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "a", new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false)) {
@Override
Query handleBareFuzzy(String qfield, Token fuzzySlop, String termImage)
throws ParseException {
if(fuzzySlop.image.endsWith("€")) {
float fms = fuzzyMinSim;
try {
fms = Float.valueOf(fuzzySlop.image.substring(1, fuzzySlop.image.length()-1)).floatValue();
} catch (Exception ignored) { }
float value = Float.parseFloat(termImage);
return getRangeQuery(qfield, Float.toString(value-fms/2.f), Float.toString(value+fms/2.f), true, true);
}
return super.handleBareFuzzy(qfield, fuzzySlop, termImage);
}
};
assertEquals(qp.parse("a:[11.95 TO 12.95]"), qp.parse("12.45~1€"));
}
@Override
public void testStarParsing() throws Exception {
final int[] type = new int[1];
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field",
new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false)) {
@Override
protected Query getWildcardQuery(String field, String termStr) {
// override error checking of superclass
type[0] = 1;
return new TermQuery(new Term(field, termStr));
}
@Override
protected Query getPrefixQuery(String field, String termStr) {
// override error checking of superclass
type[0] = 2;
return new TermQuery(new Term(field, termStr));
}
@Override
protected Query getFieldQuery(String field, String queryText,
boolean quoted) throws ParseException {
type[0] = 3;
return super.getFieldQuery(field, queryText, quoted);
}
};
TermQuery tq;
tq = (TermQuery) qp.parse("foo:zoo*");
assertEquals("zoo", tq.getTerm().text());
assertEquals(2, type[0]);
tq = (TermQuery) qp.parse("foo:zoo*^2");
assertEquals("zoo", tq.getTerm().text());
assertEquals(2, type[0]);
assertEquals(tq.getBoost(), 2, 0);
tq = (TermQuery) qp.parse("foo:*");
assertEquals("*", tq.getTerm().text());
assertEquals(1, type[0]); // could be a valid prefix query in the future too
tq = (TermQuery) qp.parse("foo:*^2");
assertEquals("*", tq.getTerm().text());
assertEquals(1, type[0]);
assertEquals(tq.getBoost(), 2, 0);
tq = (TermQuery) qp.parse("*:foo");
assertEquals("*", tq.getTerm().field());
assertEquals("foo", tq.getTerm().text());
assertEquals(3, type[0]);
tq = (TermQuery) qp.parse("*:*");
assertEquals("*", tq.getTerm().field());
assertEquals("*", tq.getTerm().text());
assertEquals(1, type[0]); // could be handled as a prefix query in the
// future
tq = (TermQuery) qp.parse("(*:*)");
assertEquals("*", tq.getTerm().field());
assertEquals("*", tq.getTerm().text());
assertEquals(1, type[0]);
}
public void testCustomQueryParserWildcard() {
try {
new QPTestParser("contents", new MockAnalyzer(random(),
MockTokenizer.WHITESPACE, false)).parse("a?t");
fail("Wildcard queries should not be allowed");
} catch (ParseException expected) {
// expected exception
}
}
public void testCustomQueryParserFuzzy() throws Exception {
try {
new QPTestParser("contents", new MockAnalyzer(random(),
MockTokenizer.WHITESPACE, false)).parse("xunit~");
fail("Fuzzy queries should not be allowed");
} catch (ParseException expected) {
// expected exception
}
}
/** query parser that doesn't expand synonyms when users use double quotes */
private class SmartQueryParser extends QueryParser {
Analyzer morePrecise = new Analyzer2();
public SmartQueryParser() {
super(TEST_VERSION_CURRENT, "field", new Analyzer1());
}
@Override
protected Query getFieldQuery(String field, String queryText, boolean quoted)
throws ParseException {
if (quoted) return newFieldQuery(morePrecise, field, queryText, quoted);
else return super.getFieldQuery(field, queryText, quoted);
}
}
@Override
public void testNewFieldQuery() throws Exception {
/** ordinary behavior, synonyms form uncoordinated boolean query */
QueryParser dumb = new QueryParser(TEST_VERSION_CURRENT, "field",
new Analyzer1());
BooleanQuery expanded = new BooleanQuery(true);
expanded.add(new TermQuery(new Term("field", "dogs")),
BooleanClause.Occur.SHOULD);
expanded.add(new TermQuery(new Term("field", "dog")),
BooleanClause.Occur.SHOULD);
assertEquals(expanded, dumb.parse("\"dogs\""));
/** even with the phrase operator the behavior is the same */
assertEquals(expanded, dumb.parse("dogs"));
/**
* custom behavior, the synonyms are expanded, unless you use quote operator
*/
QueryParser smart = new SmartQueryParser();
assertEquals(expanded, smart.parse("dogs"));
Query unexpanded = new TermQuery(new Term("field", "dogs"));
assertEquals(unexpanded, smart.parse("\"dogs\""));
}
// TODO: fold these into QueryParserTestBase
/** adds synonym of "dog" for "dogs". */
static class MockSynonymAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
MockTokenizer tokenizer = new MockTokenizer();
return new TokenStreamComponents(tokenizer, new MockSynonymFilter(tokenizer));
}
}
/** simple synonyms test */
public void testSynonyms() throws Exception {
BooleanQuery expected = new BooleanQuery(true);
expected.add(new TermQuery(new Term("field", "dogs")), BooleanClause.Occur.SHOULD);
expected.add(new TermQuery(new Term("field", "dog")), BooleanClause.Occur.SHOULD);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockSynonymAnalyzer());
assertEquals(expected, qp.parse("dogs"));
assertEquals(expected, qp.parse("\"dogs\""));
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("dogs"));
assertEquals(expected, qp.parse("\"dogs\""));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("dogs^2"));
assertEquals(expected, qp.parse("\"dogs\"^2"));
}
/** forms multiphrase query */
public void testSynonymsPhrase() throws Exception {
MultiPhraseQuery expected = new MultiPhraseQuery();
expected.add(new Term("field", "old"));
expected.add(new Term[] { new Term("field", "dogs"), new Term("field", "dog") });
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockSynonymAnalyzer());
assertEquals(expected, qp.parse("\"old dogs\""));
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("\"old dogs\""));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("\"old dogs\"^2"));
expected.setSlop(3);
assertEquals(expected, qp.parse("\"old dogs\"~3^2"));
}
/**
* adds synonym of "國" for "国".
*/
protected static class MockCJKSynonymFilter extends TokenFilter {
CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
boolean addSynonym = false;
public MockCJKSynonymFilter(TokenStream input) {
super(input);
}
@Override
public final boolean incrementToken() throws IOException {
if (addSynonym) { // inject our synonym
clearAttributes();
termAtt.setEmpty().append("國");
posIncAtt.setPositionIncrement(0);
addSynonym = false;
return true;
}
if (input.incrementToken()) {
addSynonym = termAtt.toString().equals("国");
return true;
} else {
return false;
}
}
}
static class MockCJKSynonymAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer tokenizer = new SimpleCJKTokenizer();
return new TokenStreamComponents(tokenizer, new MockCJKSynonymFilter(tokenizer));
}
}
/** simple CJK synonym test */
public void testCJKSynonym() throws Exception {
BooleanQuery expected = new BooleanQuery(true);
expected.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
expected.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
assertEquals(expected, qp.parse("国"));
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("国"));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("国^2"));
}
/** synonyms with default OR operator */
public void testCJKSynonymsOR() throws Exception {
BooleanQuery expected = new BooleanQuery();
expected.add(new TermQuery(new Term("field", "中")), BooleanClause.Occur.SHOULD);
BooleanQuery inner = new BooleanQuery(true);
inner.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner, BooleanClause.Occur.SHOULD);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
assertEquals(expected, qp.parse("中国"));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("中国^2"));
}
/** more complex synonyms with default OR operator */
public void testCJKSynonymsOR2() throws Exception {
BooleanQuery expected = new BooleanQuery();
expected.add(new TermQuery(new Term("field", "中")), BooleanClause.Occur.SHOULD);
BooleanQuery inner = new BooleanQuery(true);
inner.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner, BooleanClause.Occur.SHOULD);
BooleanQuery inner2 = new BooleanQuery(true);
inner2.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner2.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner2, BooleanClause.Occur.SHOULD);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
assertEquals(expected, qp.parse("中国国"));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("中国国^2"));
}
/** synonyms with default AND operator */
public void testCJKSynonymsAND() throws Exception {
BooleanQuery expected = new BooleanQuery();
expected.add(new TermQuery(new Term("field", "中")), BooleanClause.Occur.MUST);
BooleanQuery inner = new BooleanQuery(true);
inner.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner, BooleanClause.Occur.MUST);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("中国"));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("中国^2"));
}
/** more complex synonyms with default AND operator */
public void testCJKSynonymsAND2() throws Exception {
BooleanQuery expected = new BooleanQuery();
expected.add(new TermQuery(new Term("field", "中")), BooleanClause.Occur.MUST);
BooleanQuery inner = new BooleanQuery(true);
inner.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner, BooleanClause.Occur.MUST);
BooleanQuery inner2 = new BooleanQuery(true);
inner2.add(new TermQuery(new Term("field", "国")), BooleanClause.Occur.SHOULD);
inner2.add(new TermQuery(new Term("field", "國")), BooleanClause.Occur.SHOULD);
expected.add(inner2, BooleanClause.Occur.MUST);
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("中国国"));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("中国国^2"));
}
/** forms multiphrase query */
public void testCJKSynonymsPhrase() throws Exception {
MultiPhraseQuery expected = new MultiPhraseQuery();
expected.add(new Term("field", "中"));
expected.add(new Term[] { new Term("field", "国"), new Term("field", "國")});
QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "field", new MockCJKSynonymAnalyzer());
qp.setDefaultOperator(Operator.AND);
assertEquals(expected, qp.parse("\"中国\""));
expected.setBoost(2.0f);
assertEquals(expected, qp.parse("\"中国\"^2"));
expected.setSlop(3);
assertEquals(expected, qp.parse("\"中国\"~3^2"));
}
}