diff --git a/src/main/java/org/codelibs/fess/query/parser/QueryParser.java b/src/main/java/org/codelibs/fess/query/parser/QueryParser.java index b4ca22d26..249518040 100644 --- a/src/main/java/org/codelibs/fess/query/parser/QueryParser.java +++ b/src/main/java/org/codelibs/fess/query/parser/QueryParser.java @@ -25,7 +25,10 @@ import org.apache.lucene.analysis.core.WhitespaceAnalyzer; 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; @@ -53,7 +56,7 @@ public class QueryParser { } 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; @@ -109,4 +112,43 @@ public class QueryParser { 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 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 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); + } + } } diff --git a/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java b/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java index 7aa47963e..84c9b88a7 100644 --- a/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java @@ -234,7 +234,9 @@ public class QueryHelperTest extends UnitFessTestCase { 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}}", diff --git a/src/test/java/org/codelibs/fess/query/parser/QueryParserTest.java b/src/test/java/org/codelibs/fess/query/parser/QueryParserTest.java new file mode 100644 index 000000000..253522bb4 --- /dev/null +++ b/src/test/java/org/codelibs/fess/query/parser/QueryParserTest.java @@ -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 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()); + + } + +}