diff --git a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java index 28c44a185..92b6452fd 100644 --- a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java +++ b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java @@ -1110,6 +1110,15 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** The key of the configuration. e.g. true */ String QUERY_BOOST_FUZZY_CONTENT_TRANSPOSITIONS = "query.boost.fuzzy.content.transpositions"; + /** The key of the configuration. e.g. bool */ + String QUERY_DEFAULT_query_type = "query.default.query_type"; + + /** The key of the configuration. e.g. 0.1 */ + String QUERY_DISMAX_tie_breaker = "query.dismax.tie_breaker"; + + /** The key of the configuration. e.g. */ + String QUERY_BOOL_minimum_should_match = "query.bool.minimum_should_match"; + /** The key of the configuration. e.g. 50 */ String QUERY_PREFIX_EXPANSIONS = "query.prefix.expansions"; @@ -5244,6 +5253,43 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction */ boolean isQueryBoostFuzzyContentTranspositions(); + /** + * Get the value for the key 'query.default.query_type'.
+ * The value is, e.g. bool
+ * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getQueryDefaultQueryType(); + + /** + * Get the value for the key 'query.dismax.tie_breaker'.
+ * The value is, e.g. 0.1
+ * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getQueryDismaxTieBreaker(); + + /** + * Get the value for the key 'query.dismax.tie_breaker' as {@link java.math.BigDecimal}.
+ * The value is, e.g. 0.1
+ * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not decimal. + */ + java.math.BigDecimal getQueryDismaxTieBreakerAsDecimal(); + + /** + * Get the value for the key 'query.bool.minimum_should_match'.
+ * The value is, e.g.
+ * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getQueryBoolMinimumShouldMatch(); + + /** + * Get the value for the key 'query.bool.minimum_should_match' as {@link Integer}.
+ * The value is, e.g.
+ * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getQueryBoolMinimumShouldMatchAsInteger(); + /** * Get the value for the key 'query.prefix.expansions'.
* The value is, e.g. 50
@@ -9489,6 +9535,26 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction return is(FessConfig.QUERY_BOOST_FUZZY_CONTENT_TRANSPOSITIONS); } + public String getQueryDefaultQueryType() { + return get(FessConfig.QUERY_DEFAULT_query_type); + } + + public String getQueryDismaxTieBreaker() { + return get(FessConfig.QUERY_DISMAX_tie_breaker); + } + + public java.math.BigDecimal getQueryDismaxTieBreakerAsDecimal() { + return getAsDecimal(FessConfig.QUERY_DISMAX_tie_breaker); + } + + public String getQueryBoolMinimumShouldMatch() { + return get(FessConfig.QUERY_BOOL_minimum_should_match); + } + + public Integer getQueryBoolMinimumShouldMatchAsInteger() { + return getAsInteger(FessConfig.QUERY_BOOL_minimum_should_match); + } + public String getQueryPrefixExpansions() { return get(FessConfig.QUERY_PREFIX_EXPANSIONS); } @@ -11158,6 +11224,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction defaultMap.put(FessConfig.QUERY_BOOST_FUZZY_CONTENT_EXPANSIONS, "10"); defaultMap.put(FessConfig.QUERY_BOOST_FUZZY_CONTENT_prefix_length, "0"); defaultMap.put(FessConfig.QUERY_BOOST_FUZZY_CONTENT_TRANSPOSITIONS, "true"); + defaultMap.put(FessConfig.QUERY_DEFAULT_query_type, "bool"); + defaultMap.put(FessConfig.QUERY_DISMAX_tie_breaker, "0.1"); + defaultMap.put(FessConfig.QUERY_BOOL_minimum_should_match, ""); defaultMap.put(FessConfig.QUERY_PREFIX_EXPANSIONS, "50"); defaultMap.put(FessConfig.QUERY_PREFIX_SLOP, "0"); defaultMap.put(FessConfig.QUERY_FUZZY_prefix_length, "0"); diff --git a/src/main/java/org/codelibs/fess/query/DefaultQueryBuilder.java b/src/main/java/org/codelibs/fess/query/DefaultQueryBuilder.java new file mode 100644 index 000000000..b0682e2ef --- /dev/null +++ b/src/main/java/org/codelibs/fess/query/DefaultQueryBuilder.java @@ -0,0 +1,148 @@ +/* + * Copyright 2012-2024 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; + +import java.io.IOException; +import java.util.Objects; + +import org.apache.lucene.search.Query; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.DisMaxQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilderVisitor; +import org.opensearch.index.query.QueryRewriteContext; +import org.opensearch.index.query.QueryShardContext; + +public class DefaultQueryBuilder implements QueryBuilder { + + private final QueryBuilder queryBuilder; + + private final QueryType queryType; + + public DefaultQueryBuilder(final QueryBuilder queryBuilder) { + this.queryBuilder = queryBuilder; + if (queryBuilder instanceof BoolQueryBuilder) { + queryType = QueryType.BOOL; + } else if (queryBuilder instanceof DisMaxQueryBuilder) { + queryType = QueryType.DISMAX; + } else { + throw new IllegalArgumentException("Unknown query builder: " + queryBuilder); + } + } + + public DefaultQueryBuilder add(final QueryBuilder innerQueryBuilder) { + switch (queryType) { + case BOOL: + ((BoolQueryBuilder) queryBuilder).should(innerQueryBuilder); + break; + case DISMAX: + ((DisMaxQueryBuilder) queryBuilder).add(innerQueryBuilder); + break; + default: + break; + } + return this; + } + + enum QueryType { + BOOL, DISMAX; + } + + @Override + public String getWriteableName() { + return queryBuilder.getWriteableName(); + } + + @Override + public Query toQuery(final QueryShardContext context) throws IOException { + return queryBuilder.toQuery(context); + } + + @Override + public boolean isFragment() { + return queryBuilder.isFragment(); + } + + @Override + public QueryBuilder queryName(final String queryName) { + return queryBuilder.queryName(queryName); + } + + @Override + public String queryName() { + return queryBuilder.queryName(); + } + + @Override + public float boost() { + return queryBuilder.boost(); + } + + @Override + public QueryBuilder boost(final float boost) { + return queryBuilder.boost(boost); + } + + @Override + public String getName() { + return queryBuilder.getName(); + } + + @Override + public QueryBuilder rewrite(final QueryRewriteContext queryShardContext) throws IOException { + return queryBuilder.rewrite(queryShardContext); + } + + @Override + public void visit(final QueryBuilderVisitor visitor) { + queryBuilder.visit(visitor); + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + return queryBuilder.toXContent(builder, params); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + queryBuilder.writeTo(out); + } + + @Override + public int hashCode() { + return queryBuilder.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + final DefaultQueryBuilder other = (DefaultQueryBuilder) obj; + return Objects.equals(queryBuilder, other.queryBuilder); + } + + @Override + public String toString() { + return queryBuilder.toString(); + } + +} diff --git a/src/main/java/org/codelibs/fess/query/QueryCommand.java b/src/main/java/org/codelibs/fess/query/QueryCommand.java index f7ff145a5..6b70450c8 100644 --- a/src/main/java/org/codelibs/fess/query/QueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/QueryCommand.java @@ -20,6 +20,7 @@ import static org.codelibs.core.stream.StreamUtil.stream; import java.lang.Character.UnicodeBlock; import org.apache.lucene.search.Query; +import org.codelibs.core.lang.StringUtil; import org.codelibs.fess.Constants; import org.codelibs.fess.entity.QueryContext; import org.codelibs.fess.mylasta.direction.FessConfig; @@ -27,6 +28,7 @@ import org.codelibs.fess.util.ComponentUtil; import org.dbflute.optional.OptionalThing; import org.lastaflute.web.util.LaRequestUtil; import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.DisMaxQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.search.sort.SortBuilder; @@ -72,30 +74,47 @@ public abstract class QueryCommand { (String[]) request.getAttribute(Constants.REQUEST_LANGUAGES))); } - protected BoolQueryBuilder buildDefaultQueryBuilder(final FessConfig fessConfig, final QueryContext context, + protected DefaultQueryBuilder buildDefaultQueryBuilder(final FessConfig fessConfig, final QueryContext context, final DefaultQueryBuilderFunction builder) { - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - boolQuery.should(builder.apply(fessConfig.getIndexFieldTitle(), fessConfig.getQueryBoostTitleAsDecimal().floatValue())); - boolQuery.should(builder.apply(fessConfig.getIndexFieldContent(), fessConfig.getQueryBoostContentAsDecimal().floatValue())); + final DefaultQueryBuilder defaultQuery = createDefaultQueryBuilder(); + defaultQuery.add(builder.apply(fessConfig.getIndexFieldTitle(), fessConfig.getQueryBoostTitleAsDecimal().floatValue())); + defaultQuery.add(builder.apply(fessConfig.getIndexFieldContent(), fessConfig.getQueryBoostContentAsDecimal().floatValue())); final float importantContentBoost = fessConfig.getQueryBoostImportantContentAsDecimal().floatValue(); if (importantContentBoost >= 0.0f) { - boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent(), importantContentBoost)); + defaultQuery.add(builder.apply(fessConfig.getIndexFieldImportantContent(), importantContentBoost)); } final float importantContantLangBoost = fessConfig.getQueryBoostImportantContentLangAsDecimal().floatValue(); getQueryLanguages().ifPresent(langs -> stream(langs).of(stream -> stream.forEach(lang -> { - boolQuery.should( + defaultQuery.add( builder.apply(fessConfig.getIndexFieldTitle() + "_" + lang, fessConfig.getQueryBoostTitleLangAsDecimal().floatValue())); - boolQuery.should(builder.apply(fessConfig.getIndexFieldContent() + "_" + lang, + defaultQuery.add(builder.apply(fessConfig.getIndexFieldContent() + "_" + lang, fessConfig.getQueryBoostContentLangAsDecimal().floatValue())); if (importantContantLangBoost >= 0.0f) { - boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent() + "_" + lang, importantContantLangBoost)); + defaultQuery.add(builder.apply(fessConfig.getIndexFieldImportantContent() + "_" + lang, importantContantLangBoost)); } }))); getQueryFieldConfig().additionalDefaultList.stream().forEach(f -> { final QueryBuilder query = builder.apply(f.getFirst(), f.getSecond()); - boolQuery.should(query); + defaultQuery.add(query); }); - return boolQuery; + return defaultQuery; + } + + protected DefaultQueryBuilder createDefaultQueryBuilder() { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + + if ("dismax".equals(fessConfig.getQueryDefaultQueryType())) { + final DisMaxQueryBuilder disMaxQuery = QueryBuilders.disMaxQuery(); + disMaxQuery.tieBreaker(fessConfig.getQueryDismaxTieBreakerAsDecimal().floatValue()); + return new DefaultQueryBuilder(disMaxQuery); + } + + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + final String minimumShouldMatch = fessConfig.getQueryBoolMinimumShouldMatch(); + if (StringUtil.isNotBlank(minimumShouldMatch)) { + boolQuery.minimumShouldMatch(minimumShouldMatch); + } + return new DefaultQueryBuilder(boolQuery); } protected QueryBuilder buildMatchPhraseQuery(final String f, final String text) { diff --git a/src/main/java/org/codelibs/fess/query/TermQueryCommand.java b/src/main/java/org/codelibs/fess/query/TermQueryCommand.java index 53641c461..6eb86f157 100644 --- a/src/main/java/org/codelibs/fess/query/TermQueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/TermQueryCommand.java @@ -34,7 +34,6 @@ import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.util.ComponentUtil; import org.lastaflute.core.message.UserMessages; import org.opensearch.common.unit.Fuzziness; -import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.search.sort.SortOrder; @@ -161,24 +160,24 @@ public class TermQueryCommand extends QueryCommand { final float boost, final String field, final String text) { context.addFieldLog(field, text); context.addHighlightedQuery(text); - final BoolQueryBuilder boolQuery = + final DefaultQueryBuilder defaultQuery = buildDefaultQueryBuilder(fessConfig, context, (f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost)); final Integer fuzzyMinLength = fessConfig.getQueryBoostFuzzyMinLengthAsInteger(); if (fuzzyMinLength >= 0 && text.length() >= fuzzyMinLength) { - boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldTitle(), text) + defaultQuery.add(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldTitle(), text) .boost(fessConfig.getQueryBoostFuzzyTitleAsDecimal().floatValue()) .prefixLength(fessConfig.getQueryBoostFuzzyTitlePrefixLengthAsInteger()) .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyTitleTranspositions())) .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyTitleFuzziness())) .maxExpansions(fessConfig.getQueryBoostFuzzyTitleExpansionsAsInteger())); - boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldContent(), text) + defaultQuery.add(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldContent(), text) .prefixLength(fessConfig.getQueryBoostFuzzyContentPrefixLengthAsInteger()) .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyContentTranspositions())) .boost(fessConfig.getQueryBoostFuzzyContentAsDecimal().floatValue()) .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyContentFuzziness())) .maxExpansions(fessConfig.getQueryBoostFuzzyContentExpansionsAsInteger())); } - return boolQuery; + return defaultQuery; } protected QueryBuilder convertSiteQuery(final FessConfig fessConfig, final QueryContext context, final TermQuery termQuery, diff --git a/src/main/resources/fess_config.properties b/src/main/resources/fess_config.properties index 2060037d6..6afdce293 100644 --- a/src/main/resources/fess_config.properties +++ b/src/main/resources/fess_config.properties @@ -570,6 +570,10 @@ query.boost.fuzzy.content.expansions=10 query.boost.fuzzy.content.prefix_length=0 query.boost.fuzzy.content.transpositions=true +query.default.query_type=bool +query.dismax.tie_breaker=0.1 +query.bool.minimum_should_match= + query.prefix.expansions=50 query.prefix.slop=0 query.fuzzy.prefix_length=0 diff --git a/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java b/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java index 2e586d6a0..0e1689e7f 100644 --- a/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java +++ b/src/test/java/org/codelibs/fess/helper/QueryHelperTest.java @@ -16,6 +16,7 @@ package org.codelibs.fess.helper; import java.io.File; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +28,7 @@ import org.codelibs.fess.Constants; import org.codelibs.fess.entity.QueryContext; import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; import org.codelibs.fess.exception.InvalidQueryException; +import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.query.BooleanQueryCommand; import org.codelibs.fess.query.BoostQueryCommand; import org.codelibs.fess.query.FuzzyQueryCommand; @@ -41,6 +43,7 @@ import org.codelibs.fess.query.WildcardQueryCommand; import org.codelibs.fess.query.parser.QueryParser; import org.codelibs.fess.unit.UnitFessTestCase; import org.codelibs.fess.util.ComponentUtil; +import org.dbflute.util.DfTypeUtil; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; @@ -88,7 +91,54 @@ public class QueryHelperTest extends UnitFessTestCase { new WildcardQueryCommand().register(); } + private void setQueryType(final String queryType) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + @Override + public String get(String propertyKey) { + return fessConfig.get(propertyKey); + } + + @Override + public BigDecimal getAsDecimal(String propertyKey) { + return DfTypeUtil.toBigDecimal(get(propertyKey)); + } + + @Override + public Integer getAsInteger(String propertyKey) { + return DfTypeUtil.toInteger(get(propertyKey)); + } + + @Override + public String getQueryDefaultQueryType() { + return queryType; + } + }); + } + public void test_build_simple() { + setQueryType("bool"); + + float titleBoost = 0.5f; + float contentBoost = 0.05f; + + assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), // + Map.of("_default", List.of("QUERY")), // + Set.of("QUERY"), // + buildQuery("QUERY")); + assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), // + Map.of("_default", List.of("QUERY")), // + Set.of("QUERY"), // + buildQuery(" QUERY")); + assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), // + Map.of("_default", List.of("QUERY")), // + Set.of("QUERY"), // + buildQuery("QUERY ")); + } + + public void test_build_simple_dismax() { + setQueryType("dismax"); + float titleBoost = 0.5f; float contentBoost = 0.05f; @@ -133,6 +183,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_boost() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"QUERY1\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":5.0}}},{\"match_phrase\":{\"content\":{\"query\":\"QUERY1\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"fuzzy\":{\"title\":{\"value\":\"QUERY1\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.01}}},{\"fuzzy\":{\"content\":{\"value\":\"QUERY1\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.005}}}],\"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}}", Map.of("_default", List.of("QUERY1")), // @@ -153,6 +204,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_wildcard() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"wildcard\":{\"title\":{\"wildcard\":\"*\",\"boost\":0.5}}},{\"wildcard\":{\"content\":{\"wildcard\":\"*\",\"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}}", Map.of("_default", List.of("*")), // @@ -191,6 +243,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_phrase() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"QUERY1QUERY2\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"QUERY1QUERY2\",\"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}}", Map.of("_default", List.of("QUERY1 QUERY2")), // @@ -207,6 +260,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_prefix() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase_prefix\":{\"title\":{\"query\":\"query\",\"slop\":0,\"max_expansions\":50,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase_prefix\":{\"content\":{\"query\":\"query\",\"slop\":0,\"max_expansions\":50,\"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}}", Map.of("_default", List.of("QUERY*")), // @@ -229,6 +283,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_escape() { + setQueryType("bool"); 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}}},{\"fuzzy\":{\"title\":{\"value\":\"aaa:bbb\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.01}}},{\"fuzzy\":{\"content\":{\"value\":\"aaa:bbb\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.005}}}],\"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}}", Map.of("_default", List.of("aaa:bbb")), // @@ -335,6 +390,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_fuzzy() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"fuzzy\":{\"title\":{\"value\":\"QUERY1\",\"fuzziness\":\"2\",\"prefix_length\":0,\"max_expansions\":50,\"transpositions\":true,\"boost\":0.5}}},{\"fuzzy\":{\"content\":{\"value\":\"QUERY1\",\"fuzziness\":\"2\",\"prefix_length\":0,\"max_expansions\":50,\"transpositions\":true,\"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}}", Map.of("_default", List.of("QUERY1")), // @@ -357,6 +413,7 @@ public class QueryHelperTest extends UnitFessTestCase { } public void test_build_range() { + setQueryType("bool"); assertQueryContext( "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"[aaaTObbb]\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"[aaaTObbb]\",\"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}}", Map.of("_default", List.of("[aaa TO bbb]")), // @@ -439,6 +496,14 @@ public class QueryHelperTest extends UnitFessTestCase { } private QueryBuilder simpleQuery(String query, float titleBoost, float contentBoost) { + if ("dismax".equals(ComponentUtil.getFessConfig().getQueryDefaultQueryType())) { + return QueryBuilders.disMaxQuery().tieBreaker(0.1f)// + .add(QueryBuilders.matchPhraseQuery("title", query).boost(titleBoost))// + .add(QueryBuilders.matchPhraseQuery("content", query).boost(contentBoost))// + .add(QueryBuilders.fuzzyQuery("title", query).boost(0.01f).maxExpansions(10))// + .add(QueryBuilders.fuzzyQuery("content", query).boost(0.005f).maxExpansions(10))// + ; + } return QueryBuilders.boolQuery()// .should(QueryBuilders.matchPhraseQuery("title", query).boost(titleBoost))// .should(QueryBuilders.matchPhraseQuery("content", query).boost(contentBoost))// diff --git a/src/test/java/org/codelibs/fess/query/DefaultQueryBuilderTest.java b/src/test/java/org/codelibs/fess/query/DefaultQueryBuilderTest.java new file mode 100644 index 000000000..5662b16b8 --- /dev/null +++ b/src/test/java/org/codelibs/fess/query/DefaultQueryBuilderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 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; + +import org.codelibs.fess.unit.UnitFessTestCase; +import org.opensearch.index.query.QueryBuilders; + +public class DefaultQueryBuilderTest extends UnitFessTestCase { + public void test_invalid_null() { + try { + new DefaultQueryBuilder(null); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + } + + public void test_invalid_query() { + try { + new DefaultQueryBuilder(QueryBuilders.matchAllQuery()); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + } +} diff --git a/src/test/java/org/codelibs/fess/query/TermQueryCommandTest.java b/src/test/java/org/codelibs/fess/query/TermQueryCommandTest.java index a99cfc9c3..11d50be65 100644 --- a/src/test/java/org/codelibs/fess/query/TermQueryCommandTest.java +++ b/src/test/java/org/codelibs/fess/query/TermQueryCommandTest.java @@ -15,6 +15,7 @@ */ package org.codelibs.fess.query; +import java.math.BigDecimal; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -23,9 +24,11 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.codelibs.fess.entity.QueryContext; import org.codelibs.fess.exception.InvalidQueryException; +import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.query.parser.QueryParser; import org.codelibs.fess.unit.UnitFessTestCase; import org.codelibs.fess.util.ComponentUtil; +import org.dbflute.util.DfTypeUtil; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MatchPhraseQueryBuilder; import org.opensearch.index.query.PrefixQueryBuilder; @@ -60,8 +63,34 @@ public class TermQueryCommandTest extends UnitFessTestCase { super.tearDown(); } + private void setQueryType(final String queryType) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + @Override + public String get(String propertyKey) { + return fessConfig.get(propertyKey); + } + + @Override + public BigDecimal getAsDecimal(String propertyKey) { + return DfTypeUtil.toBigDecimal(get(propertyKey)); + } + + @Override + public Integer getAsInteger(String propertyKey) { + return DfTypeUtil.toInteger(get(propertyKey)); + } + + @Override + public String getQueryDefaultQueryType() { + return queryType; + } + }); + } + public void test_convertTermQuery() throws Exception { - assertQueryBuilder(BoolQueryBuilder.class, + setQueryType("bool"); + assertQueryBuilder(DefaultQueryBuilder.class, "{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.05}}}],\"adjust_pure_negative\":true,\"boost\":1.0}}", "aaa"); assertQueryBuilder(MatchPhraseQueryBuilder.class, @@ -70,7 +99,7 @@ public class TermQueryCommandTest extends UnitFessTestCase { assertQueryBuilder(MatchPhraseQueryBuilder.class, "{\"match_phrase\":{\"content\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}}", // "content:aaa"); - assertQueryBuilder(BoolQueryBuilder.class, + assertQueryBuilder(DefaultQueryBuilder.class, "{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"xxx:aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"xxx:aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.05}}},{\"fuzzy\":{\"title\":{\"value\":\"xxx:aaa\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.01}}},{\"fuzzy\":{\"content\":{\"value\":\"xxx:aaa\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.005}}}],\"adjust_pure_negative\":true,\"boost\":1.0}}", "xxx:aaa"); assertQueryBuilder(WildcardQueryBuilder.class, // @@ -95,6 +124,42 @@ public class TermQueryCommandTest extends UnitFessTestCase { } } + public void test_convertTermQuery_dismax() throws Exception { + setQueryType("dismax"); + assertQueryBuilder(DefaultQueryBuilder.class, + "{\"dis_max\":{\"tie_breaker\":0.1,\"queries\":[{\"match_phrase\":{\"title\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.05}}}],\"boost\":1.0}}", + "aaa"); + assertQueryBuilder(MatchPhraseQueryBuilder.class, + "{\"match_phrase\":{\"title\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}}", // + "title:aaa"); + assertQueryBuilder(MatchPhraseQueryBuilder.class, + "{\"match_phrase\":{\"content\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}}", // + "content:aaa"); + assertQueryBuilder(DefaultQueryBuilder.class, + "{\"dis_max\":{\"tie_breaker\":0.1,\"queries\":[{\"match_phrase\":{\"title\":{\"query\":\"xxx:aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"xxx:aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.05}}},{\"fuzzy\":{\"title\":{\"value\":\"xxx:aaa\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.01}}},{\"fuzzy\":{\"content\":{\"value\":\"xxx:aaa\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.005}}}],\"boost\":1.0}}", + "xxx:aaa"); + assertQueryBuilder(WildcardQueryBuilder.class, // + "{\"wildcard\":{\"url\":{\"wildcard\":\"*aaa*\",\"boost\":1.0}}}", // + "inurl:aaa"); + assertQueryBuilder(TermQueryBuilder.class, // + "{\"term\":{\"url\":{\"value\":\"aaa\",\"boost\":1.0}}}", // + "url:aaa"); + assertQueryBuilder(PrefixQueryBuilder.class, // + "{\"prefix\":{\"site\":{\"value\":\"aaa\",\"boost\":1.0}}}", // + "site:aaa"); + + assertQueryBuilder("{\"timestamp\":{\"order\":\"asc\"}}", "sort:timestamp"); + assertQueryBuilder("{\"timestamp\":{\"order\":\"asc\"}}", "sort:timestamp.asc"); + assertQueryBuilder("{\"timestamp\":{\"order\":\"desc\"}}", "sort:timestamp.desc"); + + try { + assertQueryBuilder("", "sort:xxx"); + fail(); + } catch (InvalidQueryException e) { + // nothing + } + } + private void assertQueryBuilder(final String expect, final String text) throws Exception { QueryContext queryContext = assertQueryBuilder(null, null, text); List> sortBuilders = queryContext.sortBuilders();