Skip to content

Commit

Permalink
fix #2778 Implement phrase query handling for single-word terms enclo…
Browse files Browse the repository at this point in the history
…sed in quotes
  • Loading branch information
marevol committed Nov 2, 2023
1 parent 79cd2a6 commit 6d0530a
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 2 deletions.
44 changes: 43 additions & 1 deletion src/main/java/org/codelibs/fess/query/parser/QueryParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser.Operator;
import org.apache.lucene.queryparser.ext.ExtendableQueryParser;
import org.apache.lucene.queryparser.ext.Extensions.Pair;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.codelibs.fess.Constants;
import org.codelibs.fess.exception.QueryParseException;

Expand Down Expand Up @@ -53,7 +56,7 @@ public Query parse(final String query) {
}

protected org.apache.lucene.queryparser.classic.QueryParser createQueryParser() {
final ExtendableQueryParser parser = new ExtendableQueryParser(defaultField, analyzer);
final LuceneQueryParser parser = new LuceneQueryParser(defaultField, analyzer);
parser.setAllowLeadingWildcard(allowLeadingWildcard);
parser.setDefaultOperator(defaultOperator);
return parser;
Expand Down Expand Up @@ -109,4 +112,43 @@ public interface Filter {
public interface FilterChain {
Query parse(final String query);
}

protected static class LuceneQueryParser extends org.apache.lucene.queryparser.classic.QueryParser {

private final String defaultField;

/**
* Creates a new {@link ExtendableQueryParser} instance
*
* @param f the default query field
* @param a the analyzer used to find terms in a query string
*/
public LuceneQueryParser(final String f, final Analyzer a) {
super(f, a);
this.defaultField = f;
}

@Override
protected Query getFieldQuery(final String field, final String queryText, boolean quoted) throws ParseException {
final org.apache.lucene.search.Query query = super.getFieldQuery(field, queryText, quoted);
if (quoted && query instanceof TermQuery termQuery) {
final Pair<String, String> splitField = splitField(defaultField, field);
if (defaultField.equals(splitField.cur)) {
final PhraseQuery.Builder builder = new PhraseQuery.Builder();
builder.add(termQuery.getTerm());
return builder.build();
}
}
return query;
}

protected Pair<String, String> splitField(String defaultField, String field) {
int indexOf = field.indexOf(':');
if (indexOf < 0)
return new Pair<>(field, null);
final String indexField = indexOf == 0 ? defaultField : field.substring(0, indexOf);
final String extensionKey = field.substring(indexOf + 1);
return new Pair<>(indexField, extensionKey);
}
}
}
4 changes: 3 additions & 1 deletion src/test/java/org/codelibs/fess/helper/QueryHelperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ public void test_build_escape() {
Map.of("_default", List.of("aaa:bbb")), //
Set.of("aaa:bbb"), //
buildQuery("aaa\\:bbb"));
assertQueryContext(buildQuery("aaa\\:bbb").getQueryBuilder().toString().replaceAll("\\s", ""), buildQuery("\"aaa\\:bbb\""));
assertQueryContext(
"{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"aaa:bbb\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"aaa:bbb\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.05}}}],\"adjust_pure_negative\":true,\"boost\":1.0}},\"functions\":[{\"filter\":{\"match_all\":{\"boost\":1.0}},\"field_value_factor\":{\"field\":\"boost\",\"factor\":1.0,\"modifier\":\"none\"}}],\"score_mode\":\"multiply\",\"max_boost\":3.4028235E38,\"boost\":1.0}}",
buildQuery("\"aaa\\:bbb\""));

assertQueryContext(
"{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"aaa:bbb\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}},\"functions\":[{\"filter\":{\"match_all\":{\"boost\":1.0}},\"field_value_factor\":{\"field\":\"boost\",\"factor\":1.0,\"modifier\":\"none\"}}],\"score_mode\":\"multiply\",\"max_boost\":3.4028235E38,\"boost\":1.0}}",
Expand Down
113 changes: 113 additions & 0 deletions src/test/java/org/codelibs/fess/query/parser/QueryParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2012-2023 CodeLibs Project and the Others.
*
* Licensed 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.codelibs.fess.query.parser;

import java.util.List;

import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.codelibs.fess.unit.UnitFessTestCase;

public class QueryParserTest extends UnitFessTestCase {

public void test_LuceneQueryParser() {
QueryParser queryParser = new QueryParser();

Query query = queryParser.createDefaultFilterChain().parse("fess");
assertEquals(TermQuery.class, query.getClass());
assertEquals("_default:fess", ((TermQuery) query).getTerm().toString());

query = queryParser.createDefaultFilterChain().parse("title:fess");
assertEquals(TermQuery.class, query.getClass());
assertEquals("title:fess", ((TermQuery) query).getTerm().toString());

query = queryParser.createDefaultFilterChain().parse("fess*");
assertEquals(PrefixQuery.class, query.getClass());
assertEquals("_default:fess", ((PrefixQuery) query).getPrefix().toString());

query = queryParser.createDefaultFilterChain().parse("fe?s");
assertEquals(WildcardQuery.class, query.getClass());
assertEquals("_default:fe?s", ((WildcardQuery) query).getTerm().toString());

query = queryParser.createDefaultFilterChain().parse("fess~");
assertEquals(FuzzyQuery.class, query.getClass());
assertEquals("_default:fess", ((FuzzyQuery) query).getTerm().toString());

query = queryParser.createDefaultFilterChain().parse("fess^10");
assertEquals(BoostQuery.class, query.getClass());
assertEquals("_default:fess", ((BoostQuery) query).getQuery().toString());
assertEquals(10.0f, ((BoostQuery) query).getBoost());

query = queryParser.createDefaultFilterChain().parse("\"fess\"");
assertEquals(PhraseQuery.class, query.getClass());
assertEquals("_default:fess", ((PhraseQuery) query).getTerms()[0].toString());

query = queryParser.createDefaultFilterChain().parse("\"fess codelibs\"");
assertEquals(PhraseQuery.class, query.getClass());
assertEquals("_default:fess", ((PhraseQuery) query).getTerms()[0].toString());
assertEquals("_default:codelibs", ((PhraseQuery) query).getTerms()[1].toString());

query = queryParser.createDefaultFilterChain().parse("fess codelibs");
assertEquals(BooleanQuery.class, query.getClass());
List<BooleanClause> clauses = ((BooleanQuery) query).clauses();
assertEquals(TermQuery.class, clauses.get(0).getQuery().getClass());
assertEquals("_default:fess", clauses.get(0).getQuery().toString());
assertEquals(Occur.MUST, clauses.get(0).getOccur());
assertEquals(TermQuery.class, clauses.get(1).getQuery().getClass());
assertEquals("_default:codelibs", clauses.get(1).getQuery().toString());
assertEquals(Occur.MUST, clauses.get(1).getOccur());

query = queryParser.createDefaultFilterChain().parse("fess AND codelibs");
assertEquals(BooleanQuery.class, query.getClass());
clauses = ((BooleanQuery) query).clauses();
assertEquals(TermQuery.class, clauses.get(0).getQuery().getClass());
assertEquals("_default:fess", clauses.get(0).getQuery().toString());
assertEquals(Occur.MUST, clauses.get(0).getOccur());
assertEquals(TermQuery.class, clauses.get(1).getQuery().getClass());
assertEquals("_default:codelibs", clauses.get(1).getQuery().toString());
assertEquals(Occur.MUST, clauses.get(1).getOccur());

query = queryParser.createDefaultFilterChain().parse("fess OR codelibs");
assertEquals(BooleanQuery.class, query.getClass());
clauses = ((BooleanQuery) query).clauses();
assertEquals(TermQuery.class, clauses.get(0).getQuery().getClass());
assertEquals("_default:fess", clauses.get(0).getQuery().toString());
assertEquals(Occur.SHOULD, clauses.get(0).getOccur());
assertEquals(TermQuery.class, clauses.get(1).getQuery().getClass());
assertEquals("_default:codelibs", clauses.get(1).getQuery().toString());
assertEquals(Occur.SHOULD, clauses.get(1).getOccur());

query = queryParser.createDefaultFilterChain().parse("\"fess\" codelibs");
assertEquals(BooleanQuery.class, query.getClass());
clauses = ((BooleanQuery) query).clauses();
assertEquals(PhraseQuery.class, clauses.get(0).getQuery().getClass());
assertEquals("_default:fess", ((PhraseQuery) clauses.get(0).getQuery()).getTerms()[0].toString());
assertEquals(Occur.MUST, clauses.get(0).getOccur());
assertEquals(TermQuery.class, clauses.get(1).getQuery().getClass());
assertEquals("_default:codelibs", clauses.get(1).getQuery().toString());
assertEquals(Occur.MUST, clauses.get(1).getOccur());

}

}

0 comments on commit 6d0530a

Please sign in to comment.