浏览代码

fix #2778 Implement phrase query handling for single-word terms enclosed in quotes

Shinsuke Sugaya 1 年之前
父节点
当前提交
6d0530a778

+ 43 - 1
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<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);
+        }
+    }
 }

+ 3 - 1
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}}",

+ 113 - 0
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<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());
+
+    }
+
+}