Shinsuke Sugaya преди 3 години
родител
ревизия
0579b9473c

+ 7 - 7
src/main/java/org/codelibs/fess/entity/QueryContext.java

@@ -43,19 +43,19 @@ public class QueryContext {
 
 
     protected static final String ALLINTITLE_FIELD_PREFIX = "allintitle:";
     protected static final String ALLINTITLE_FIELD_PREFIX = "allintitle:";
 
 
-    private QueryBuilder queryBuilder;
+    protected QueryBuilder queryBuilder;
 
 
-    private final List<SortBuilder<?>> sortBuilderList = new ArrayList<>();
+    protected final List<SortBuilder<?>> sortBuilderList = new ArrayList<>();
 
 
-    private String queryString;
+    protected String queryString;
 
 
-    private Set<String> highlightedQuerySet = null;
+    protected Set<String> highlightedQuerySet = null;
 
 
-    private Map<String, List<String>> fieldLogMap = null;
+    protected Map<String, List<String>> fieldLogMap = null;
 
 
-    private boolean disableRoleQuery = false;
+    protected boolean disableRoleQuery = false;
 
 
-    private String defaultField = null;
+    protected String defaultField = null;
 
 
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     public QueryContext(final String queryString, final boolean isQuery) {
     public QueryContext(final String queryString, final boolean isQuery) {

+ 13 - 7
src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java

@@ -54,23 +54,29 @@ public class FuzzyQueryCommand extends QueryCommand {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final Term term = fuzzyQuery.getTerm();
         final Term term = fuzzyQuery.getTerm();
         final String field = getSearchField(context.getDefaultField(), term.field());
         final String field = getSearchField(context.getDefaultField(), term.field());
-        // TODO fuzzy value
+
         if (Constants.DEFAULT_FIELD.equals(field)) {
         if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, term.text());
+            final String text = term.text();
+            context.addFieldLog(field, text);
+            context.addHighlightedQuery(text);
             return buildDefaultQueryBuilder(fessConfig, context,
             return buildDefaultQueryBuilder(fessConfig, context,
-                    (f, b) -> QueryBuilders.fuzzyQuery(f, term.text()).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
-                            .boost(b * boost).maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
+                    (f, b) -> QueryBuilders.fuzzyQuery(f, text).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits())).boost(b * boost)
+                            .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
                             .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
                             .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
                             .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
                             .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
         }
         }
+
         if (isSearchField(field)) {
         if (isSearchField(field)) {
-            context.addFieldLog(field, term.text());
-            return QueryBuilders.fuzzyQuery(field, term.text()).boost(boost).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
+            final String text = term.text();
+            context.addFieldLog(field, text);
+            context.addHighlightedQuery(text);
+            return QueryBuilders.fuzzyQuery(field, text).boost(boost).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
                     .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
                     .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
                     .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
                     .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
                     .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions()));
                     .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions()));
         }
         }
-        final String origQuery = fuzzyQuery.toString();
+
+        final String origQuery = term.toString();
         context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
         context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
         context.addHighlightedQuery(origQuery);
         context.addHighlightedQuery(origQuery);
         return buildDefaultQueryBuilder(fessConfig, context,
         return buildDefaultQueryBuilder(fessConfig, context,

+ 15 - 2
src/main/java/org/codelibs/fess/query/PhraseQueryCommand.java

@@ -22,6 +22,7 @@ import org.apache.logging.log4j.Logger;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Query;
+import org.codelibs.fess.Constants;
 import org.codelibs.fess.entity.QueryContext;
 import org.codelibs.fess.entity.QueryContext;
 import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.mylasta.direction.FessConfig;
@@ -59,8 +60,20 @@ public class PhraseQueryCommand extends QueryCommand {
         final String field = terms[0].field();
         final String field = terms[0].field();
         final String[] texts = stream(terms).get(stream -> stream.map(Term::text).toArray(n -> new String[n]));
         final String[] texts = stream(terms).get(stream -> stream.map(Term::text).toArray(n -> new String[n]));
         final String text = String.join(" ", texts);
         final String text = String.join(" ", texts);
-        context.addFieldLog(field, text);
-        stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
+
+        if (Constants.DEFAULT_FIELD.equals(field)) {
+            context.addFieldLog(field, text);
+            stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
+            return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
+        }
+
+        if (isSearchField(field)) {
+            context.addFieldLog(field, text);
+            stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
+            return buildMatchPhraseQuery(field, text);
+        }
+
+        context.addFieldLog(Constants.DEFAULT_FIELD, text);
         return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
         return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
     }
     }
 
 

+ 11 - 5
src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java

@@ -55,12 +55,16 @@ public class PrefixQueryCommand extends QueryCommand {
     protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
     protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final String field = getSearchField(context.getDefaultField(), prefixQuery.getField());
         final String field = getSearchField(context.getDefaultField(), prefixQuery.getField());
+        final String text = prefixQuery.getPrefix().text();
+
         if (Constants.DEFAULT_FIELD.equals(field)) {
         if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, prefixQuery.getPrefix().text());
+            context.addFieldLog(field, text + "*");
+            context.addHighlightedQuery(text);
             return buildDefaultQueryBuilder(fessConfig, context,
             return buildDefaultQueryBuilder(fessConfig, context,
-                    (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(b * boost)
+                    (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, toLowercaseWildcard(text)).boost(b * boost)
                             .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
                             .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
         }
         }
+
         if (!isSearchField(field)) {
         if (!isSearchField(field)) {
             final String query = prefixQuery.getPrefix().toString();
             final String query = prefixQuery.getPrefix().toString();
             final String origQuery = toLowercaseWildcard(query);
             final String origQuery = toLowercaseWildcard(query);
@@ -70,11 +74,13 @@ public class PrefixQueryCommand extends QueryCommand {
                     (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, origQuery).boost(b * boost)
                     (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, origQuery).boost(b * boost)
                             .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
                             .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
         }
         }
-        context.addFieldLog(field, prefixQuery.getPrefix().text() + "*");
+
+        context.addFieldLog(field, text + "*");
+        context.addHighlightedQuery(text);
         if (getQueryFieldConfig().notAnalyzedFieldSet.contains(field)) {
         if (getQueryFieldConfig().notAnalyzedFieldSet.contains(field)) {
-            return QueryBuilders.prefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost);
+            return QueryBuilders.prefixQuery(field, toLowercaseWildcard(text)).boost(boost);
         }
         }
-        return QueryBuilders.matchPhrasePrefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost)
+        return QueryBuilders.matchPhrasePrefixQuery(field, toLowercaseWildcard(text)).boost(boost)
                 .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger());
                 .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger());
     }
     }
 
 

+ 12 - 1
src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java

@@ -17,6 +17,7 @@ package org.codelibs.fess.query;
 
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.Logger;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRef;
@@ -53,12 +54,22 @@ public class TermRangeQueryCommand extends QueryCommand {
     protected QueryBuilder convertTermRangeQuery(final QueryContext context, final TermRangeQuery termRangeQuery, final float boost) {
     protected QueryBuilder convertTermRangeQuery(final QueryContext context, final TermRangeQuery termRangeQuery, final float boost) {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final String field = getSearchField(context.getDefaultField(), termRangeQuery.getField());
         final String field = getSearchField(context.getDefaultField(), termRangeQuery.getField());
+
         if (!isSearchField(field)) {
         if (!isSearchField(field)) {
-            final String origQuery = termRangeQuery.toString();
+            final StringBuilder queryBuf = new StringBuilder();
+            queryBuf.append(termRangeQuery.includesLower() ? '[' : '{');
+            final BytesRef lowerTerm = termRangeQuery.getLowerTerm();
+            queryBuf.append(lowerTerm != null ? ("*".equals(Term.toString(lowerTerm)) ? "\\*" : Term.toString(lowerTerm)) : "*");
+            queryBuf.append(" TO ");
+            final BytesRef upperTerm = termRangeQuery.getUpperTerm();
+            queryBuf.append(upperTerm != null ? ("*".equals(Term.toString(upperTerm)) ? "\\*" : Term.toString(upperTerm)) : "*");
+            queryBuf.append(termRangeQuery.includesUpper() ? ']' : '}');
+            final String origQuery = queryBuf.toString();
             context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
             context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
             context.addHighlightedQuery(origQuery);
             context.addHighlightedQuery(origQuery);
             return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b));
             return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b));
         }
         }
+
         context.addFieldLog(field, termRangeQuery.toString(field));
         context.addFieldLog(field, termRangeQuery.toString(field));
         final RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(field);
         final RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(field);
         final BytesRef min = termRangeQuery.getLowerTerm();
         final BytesRef min = termRangeQuery.getLowerTerm();

+ 21 - 6
src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java

@@ -17,6 +17,7 @@ package org.codelibs.fess.query;
 
 
 import java.util.Locale;
 import java.util.Locale;
 
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.Logger;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Query;
@@ -56,18 +57,32 @@ public class WildcardQueryCommand extends QueryCommand {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final String field = getSearchField(context.getDefaultField(), wildcardQuery.getField());
         final String field = getSearchField(context.getDefaultField(), wildcardQuery.getField());
         if (Constants.DEFAULT_FIELD.equals(field)) {
         if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, wildcardQuery.getTerm().text());
+            final String text = wildcardQuery.getTerm().text();
+            context.addFieldLog(field, text);
+            context.addHighlightedQuery(StringUtils.strip(text, "*"));
             return buildDefaultQueryBuilder(fessConfig, context,
             return buildDefaultQueryBuilder(fessConfig, context,
-                    (f, b) -> QueryBuilders.wildcardQuery(f, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(b * boost));
+                    (f, b) -> QueryBuilders.wildcardQuery(f, toLowercaseWildcard(text)).boost(b * boost));
         }
         }
+
         if (isSearchField(field)) {
         if (isSearchField(field)) {
-            context.addFieldLog(field, wildcardQuery.getTerm().text());
-            return QueryBuilders.wildcardQuery(field, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(boost);
+            final String text = wildcardQuery.getTerm().text();
+            context.addFieldLog(field, text);
+            context.addHighlightedQuery(StringUtils.strip(text, "*"));
+            return QueryBuilders.wildcardQuery(field, toLowercaseWildcard(text)).boost(boost);
         }
         }
+
         final String query = wildcardQuery.getTerm().toString();
         final String query = wildcardQuery.getTerm().toString();
-        final String origQuery = "*" + toLowercaseWildcard(query) + "*";
+        final StringBuilder queryBuf = new StringBuilder(query.length() + 2);
+        if (!query.startsWith("*")) {
+            queryBuf.append('*');
+        }
+        queryBuf.append(toLowercaseWildcard(query));
+        if (!query.endsWith("*")) {
+            queryBuf.append('*');
+        }
+        final String origQuery = queryBuf.toString();
         context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
         context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
-        context.addHighlightedQuery(query);
+        context.addHighlightedQuery(StringUtils.strip(query, "*"));
         return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> QueryBuilders.wildcardQuery(f, origQuery).boost(b * boost));
         return buildDefaultQueryBuilder(fessConfig, context, (f, b) -> QueryBuilders.wildcardQuery(f, origQuery).boost(b * boost));
     }
     }
 
 

+ 356 - 28
src/test/java/org/codelibs/fess/helper/QueryHelperTest.java

@@ -16,6 +16,10 @@
 package org.codelibs.fess.helper;
 package org.codelibs.fess.helper;
 
 
 import java.io.File;
 import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 
 import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
 import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
 import org.apache.lucene.queryparser.classic.QueryParser;
 import org.apache.lucene.queryparser.classic.QueryParser;
@@ -23,7 +27,9 @@ import org.apache.lucene.queryparser.ext.ExtendableQueryParser;
 import org.codelibs.core.io.FileUtil;
 import org.codelibs.core.io.FileUtil;
 import org.codelibs.core.misc.DynamicProperties;
 import org.codelibs.core.misc.DynamicProperties;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
+import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.query.BooleanQueryCommand;
 import org.codelibs.fess.query.BooleanQueryCommand;
 import org.codelibs.fess.query.BoostQueryCommand;
 import org.codelibs.fess.query.BoostQueryCommand;
 import org.codelibs.fess.query.FuzzyQueryCommand;
 import org.codelibs.fess.query.FuzzyQueryCommand;
@@ -85,57 +91,334 @@ public class QueryHelperTest extends UnitFessTestCase {
         new WildcardQueryCommand().register();
         new WildcardQueryCommand().register();
     }
     }
 
 
-    public void test_build() {
+    public void test_build_simple() {
         float titleBoost = 0.5f;
         float titleBoost = 0.5f;
         float contentBoost = 0.05f;
         float contentBoost = 0.05f;
 
 
-        assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), buildQuery("QUERY"));
-        assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), buildQuery(" QUERY"));
-        assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), 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"));
+        assertQuery(functionScoreQuery(simpleQuery("QUERY", titleBoost, contentBoost)), //
+                Map.of("_default", List.of("QUERY")), //
+                Set.of("QUERY"), //
+                buildQuery("QUERY "));
+    }
+
+    public void test_build_multiple() {
+        float titleBoost = 0.5f;
+        float contentBoost = 0.05f;
 
 
         assertQuery(
         assertQuery(
                 functionScoreQuery(
                 functionScoreQuery(
                         andQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
                         andQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
+                Map.of("_default", List.of("QUERY1", "QUERY2")), //
+                Set.of("QUERY1", "QUERY2"), //
                 buildQuery("QUERY1 QUERY2"));
                 buildQuery("QUERY1 QUERY2"));
         assertQuery(
         assertQuery(
                 functionScoreQuery(
                 functionScoreQuery(
                         andQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
                         andQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
+                Map.of("_default", List.of("QUERY1", "QUERY2")), //
+                Set.of("QUERY1", "QUERY2"), //
                 buildQuery("QUERY1 AND QUERY2"));
                 buildQuery("QUERY1 AND QUERY2"));
 
 
         assertQuery(
         assertQuery(
                 functionScoreQuery(
                 functionScoreQuery(
                         orQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
                         orQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
+                Map.of("_default", List.of("QUERY1", "QUERY2")), //
+                Set.of("QUERY1", "QUERY2"), //
                 buildQuery("QUERY1 OR QUERY2"));
                 buildQuery("QUERY1 OR QUERY2"));
 
 
-        assertEquals(
+    }
+
+    public void test_build_boost() {
+        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")), //
+                Set.of("QUERY1"), //
+                buildQuery("QUERY1^10"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"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}},{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"QUERY2\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":2.5}}},{\"match_phrase\":{\"content\":{\"query\":\"QUERY2\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.25}}},{\"fuzzy\":{\"title\":{\"value\":\"QUERY2\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.01}}},{\"fuzzy\":{\"content\":{\"value\":\"QUERY2\",\"fuzziness\":\"AUTO\",\"prefix_length\":0,\"max_expansions\":10,\"transpositions\":true,\"boost\":0.005}}}],\"adjust_pure_negative\":true,\"boost\":1.0}}],\"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")), //
+                Set.of("QUERY1", "QUERY2"), //
+                buildQuery("QUERY1^10 QUERY2^5"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"QUERY1\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":10.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("title", List.of("QUERY1")), //
+                Set.of("QUERY1"), //
+                buildQuery("title:QUERY1^10"));
+    }
+
+    public void test_build_wildcard() {
+        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("*")), //
+                Set.of(), //
+                buildQuery("*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"wildcard\":{\"title\":{\"wildcard\":\"*\",\"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("title", List.of("*")), //
+                Set.of(), //
+                buildQuery("title:*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"wildcard\":{\"title\":{\"wildcard\":\"*aaa*\",\"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("title", List.of("*aaa*")), //
+                Set.of("aaa"), //
+                buildQuery("title:*aaa*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"wildcard\":{\"title\":{\"wildcard\":\"*aaa:*\",\"boost\":0.5}}},{\"wildcard\":{\"content\":{\"wildcard\":\"*aaa:*\",\"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:*")), //
+                Set.of("aaa:"), //
+                buildQuery("aaa:*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"wildcard\":{\"title\":{\"wildcard\":\"*aaa*\",\"boost\":0.5}}},{\"wildcard\":{\"content\":{\"wildcard\":\"*aaa*\",\"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*")), //
+                Set.of("aaa"), //
+                buildQuery("*aaa*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"wildcard\":{\"title\":{\"wildcard\":\"*aaa:*bbb*\",\"boost\":0.5}}},{\"wildcard\":{\"content\":{\"wildcard\":\"*aaa:*bbb*\",\"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:*bbb*")), //
+                Set.of("aaa:*bbb"), //
+                buildQuery("aaa:*bbb*"));
+    }
+
+    public void test_build_phrase() {
+        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")), //
+                Set.of("QUERY1", "QUERY2"), //
+                buildQuery("\"QUERY1 QUERY2\""));
+        assertQueryContext(buildQuery("\"QUERY1 QUERY2\"").getQueryBuilder().toString().replaceAll("\\s", ""),
+                buildQuery("aaa:\"QUERY1 QUERY2\""));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"QUERY1QUERY2\",\"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}}",
+                Map.of("title", List.of("QUERY1 QUERY2")), //
+                Set.of("QUERY1", "QUERY2"), //
+                buildQuery("title:\"QUERY1 QUERY2\""));
+    }
+
+    public void test_build_prefix() {
+        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*")), //
+                Set.of("QUERY"), //
+                buildQuery("QUERY*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"match_phrase_prefix\":{\"title\":{\"query\":\"query\",\"slop\":0,\"max_expansions\":50,\"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}}",
+                Map.of("title", List.of("QUERY*")), //
+                Set.of("QUERY"), //
+                buildQuery("title:QUERY*"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"prefix\":{\"url\":{\"value\":\"query\",\"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("url", List.of("QUERY*")), //
+                Set.of("QUERY"), //
+                buildQuery("url:QUERY*"));
+        assertQueryContext(buildQuery("url:QUERY*").getQueryBuilder().toString().replaceAll("\\s", ""), buildQuery("url:\"QUERY*\""));
+
+    }
+
+    public void test_build_escape() {
+        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")), //
+                Set.of("aaa:bbb"), //
+                buildQuery("aaa\\:bbb"));
+        assertQueryContext(buildQuery("aaa\\:bbb").getQueryBuilder().toString().replaceAll("\\s", ""), 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}}",
+                Map.of("title", List.of("aaa:bbb")), //
+                Set.of("aaa:bbb"), //
+                buildQuery("title:aaa\\:bbb"));
+    }
+
+    public void test_build_site() {
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"prefix\":{\"site\":{\"value\":\"fess.codelibs.org\",\"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}}",
                 "{\"function_score\":{\"query\":{\"prefix\":{\"site\":{\"value\":\"fess.codelibs.org\",\"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("site:fess.codelibs.org").toString().replaceAll("\\s", ""));
+                Map.of("site", List.of("fess.codelibs.org*")), //
+                Set.of("fess.codelibs.org"), //
+                buildQuery("site:fess.codelibs.org"));
+    }
 
 
-        assertEquals(
+    public void test_build_allintitle() {
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"title\":{\"wildcard\":\"*\",\"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}}",
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"title\":{\"wildcard\":\"*\",\"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("allintitle:").toString().replaceAll("\\s", ""));
-        assertEquals(
-                "{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"test\",\"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}}",
-                buildQuery("allintitle:test").toString().replaceAll("\\s", ""));
-        assertEquals(
+                Map.of("title", List.of("*")), //
+                Set.of(), //
+                buildQuery("allintitle:"));
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"test\",\"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}}",
                 "{\"function_score\":{\"query\":{\"match_phrase\":{\"title\":{\"query\":\"test\",\"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}}",
-                buildQuery("allintitle: test").toString().replaceAll("\\s", ""));
-        assertEquals(
+                Map.of("title", List.of("test")), //
+                Set.of("test"), //
+                buildQuery("allintitle:test"));
+        assertQueryContext(buildQuery("allintitle:test").getQueryBuilder().toString().replaceAll("\\s", ""),
+                buildQuery("allintitle: test"));
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"match_phrase\":{\"title\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}},{\"match_phrase\":{\"title\":{\"query\":\"bbb\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}}],\"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}}",
                 "{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"match_phrase\":{\"title\":{\"query\":\"aaa\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}},{\"match_phrase\":{\"title\":{\"query\":\"bbb\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":1.0}}}],\"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("allintitle: aaa bbb").toString().replaceAll("\\s", ""));
+                Map.of("title", List.of("aaa", "bbb")), //
+                Set.of("aaa", "bbb"), //
+                buildQuery("allintitle: aaa bbb"));
+    }
 
 
-        assertEquals(
+    public void test_build_allinurl() {
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"url\":{\"wildcard\":\"*\",\"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}}",
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"url\":{\"wildcard\":\"*\",\"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("allinurl:").toString().replaceAll("\\s", ""));
-        assertEquals(
-                "{\"function_score\":{\"query\":{\"wildcard\":{\"url\":{\"wildcard\":\"*test*\",\"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("allinurl:test").toString().replaceAll("\\s", ""));
-        assertEquals(
+                Map.of("url", List.of("*")), //
+                Set.of(), //
+                buildQuery("allinurl:"));
+
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"url\":{\"wildcard\":\"*test*\",\"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}}",
                 "{\"function_score\":{\"query\":{\"wildcard\":{\"url\":{\"wildcard\":\"*test*\",\"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("allinurl: test").toString().replaceAll("\\s", ""));
-        assertEquals(
+                Map.of("url", List.of("*test*")), //
+                Set.of("test"), //
+                buildQuery("allinurl:test"));
+        assertQueryContext(buildQuery("allinurl:test").getQueryBuilder().toString().replaceAll("\\s", ""), buildQuery("allinurl: test"));
+
+        assertQueryContext(
                 "{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"wildcard\":{\"url\":{\"wildcard\":\"*aaa*\",\"boost\":1.0}}},{\"wildcard\":{\"url\":{\"wildcard\":\"*bbb*\",\"boost\":1.0}}}],\"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}}",
                 "{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"wildcard\":{\"url\":{\"wildcard\":\"*aaa*\",\"boost\":1.0}}},{\"wildcard\":{\"url\":{\"wildcard\":\"*bbb*\",\"boost\":1.0}}}],\"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("allinurl: aaa bbb").toString().replaceAll("\\s", ""));
+                Map.of("url", List.of("*aaa*", "*bbb*")), //
+                Set.of("aaa", "bbb"), //
+                buildQuery("allinurl: aaa bbb"));
+    }
+
+    public void test_build_sort() {
+        String query =
+                "{\"function_score\":{\"query\":{\"match_all\":{\"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}}";
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"asc\"}}", //
+                buildQuery("sort:timestamp"));
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"asc\"}}", //
+                buildQuery("sort:timestamp.asc"));
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"desc\"}}", //
+                buildQuery("sort:timestamp.desc"));
+
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"desc\"}}{\"last_modified\":{\"order\":\"asc\"}}", //
+                buildQuery("sort:timestamp.desc sort:last_modified"));
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"desc\"}}{\"last_modified\":{\"order\":\"desc\"}}", //
+                buildQuery("sort:timestamp.desc sort:last_modified.desc"));
+
+        assertQueryContext(query, Map.of(), //
+                Set.of(), //
+                "{\"timestamp\":{\"order\":\"asc\"}}", //
+                buildQuery("sort:timestamp.xxx"));
+
+        try {
+            buildQuery("sort:aaa");
+            fail();
+        } catch (InvalidQueryException e) {
+            // ok
+        }
+    }
+
+    public void test_build_fuzzy() {
+        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")), //
+                Set.of("QUERY1"), //
+                buildQuery("QUERY1~0.5"));
+        assertQuery(buildQuery("QUERY1~0.5").getQueryBuilder(), //
+                buildQuery("QUERY1~"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"fuzzy\":{\"title\":{\"value\":\"QUERY1\",\"fuzziness\":\"2\",\"prefix_length\":0,\"max_expansions\":50,\"transpositions\":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("title", List.of("QUERY1")), //
+                Set.of("QUERY1"), //
+                buildQuery("title:QUERY1~0.5"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"fuzzy\":{\"title\":{\"value\":\"aaa:QUERY1\",\"fuzziness\":\"2\",\"prefix_length\":0,\"max_expansions\":50,\"transpositions\":true,\"boost\":0.5}}},{\"fuzzy\":{\"content\":{\"value\":\"aaa: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("aaa:QUERY1")), //
+                Set.of("aaa:QUERY1"), //
+                buildQuery("aaa:QUERY1~0.5"));
+    }
+
+    public void test_build_range() {
+        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]")), //
+                Set.of("[aaa TO bbb]"), //
+                buildQuery("[aaa TO bbb]"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"[*TObbb]\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"[*TObbb]\",\"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("[* TO bbb]")), //
+                Set.of("[* TO bbb]"), //
+                buildQuery("[* TO bbb]"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"[aaaTO*]\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"[aaaTO*]\",\"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 *]")), //
+                Set.of("[aaa TO *]"), //
+                buildQuery("[aaa TO *]"));
+
+        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}")), //
+                Set.of("{aaa TO bbb}"), //
+                buildQuery("{aaa TO bbb}"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"{*TObbb}\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"{*TObbb}\",\"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("{* TO bbb}")), //
+                Set.of("{* TO bbb}"), //
+                buildQuery("{* TO bbb}"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"bool\":{\"should\":[{\"match_phrase\":{\"title\":{\"query\":\"{aaaTO*}\",\"slop\":0,\"zero_terms_query\":\"NONE\",\"boost\":0.5}}},{\"match_phrase\":{\"content\":{\"query\":\"{aaaTO*}\",\"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 *}")), //
+                Set.of("{aaa TO *}"), //
+                buildQuery("{aaa TO *}"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":\"0\",\"to\":\"100\",\"include_lower\":true,\"include_upper\":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("timestamp", List.of("[0 TO 100]")), //
+                Set.of(), //
+                buildQuery("timestamp:[0 TO 100]"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":null,\"to\":\"100\",\"include_lower\":true,\"include_upper\":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("timestamp", List.of("[* TO 100]")), //
+                Set.of(), //
+                buildQuery("timestamp:[* TO 100]"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":\"0\",\"to\":null,\"include_lower\":true,\"include_upper\":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("timestamp", List.of("[0 TO *]")), //
+                Set.of(), //
+                buildQuery("timestamp:[0 TO *]"));
+
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":\"0\",\"to\":\"100\",\"include_lower\":false,\"include_upper\":false,\"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("timestamp", List.of("{0 TO 100}")), //
+                Set.of(), //
+                buildQuery("timestamp:{0 TO 100}"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":null,\"to\":\"100\",\"include_lower\":true,\"include_upper\":false,\"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("timestamp", List.of("{* TO 100}")), //
+                Set.of(), //
+                buildQuery("timestamp:{* TO 100}"));
+        assertQueryContext(
+                "{\"function_score\":{\"query\":{\"range\":{\"timestamp\":{\"from\":\"0\",\"to\":null,\"include_lower\":false,\"include_upper\":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("timestamp", List.of("{0 TO *}")), //
+                Set.of(), //
+                buildQuery("timestamp:{0 TO *}"));
     }
     }
 
 
     private QueryBuilder andQuery(QueryBuilder... queries) {
     private QueryBuilder andQuery(QueryBuilder... queries) {
@@ -167,14 +450,59 @@ public class QueryHelperTest extends UnitFessTestCase {
         return QueryBuilders.functionScoreQuery(queryBuilder, ScoreFunctionBuilders.fieldValueFactorFunction("boost"));
         return QueryBuilders.functionScoreQuery(queryBuilder, ScoreFunctionBuilders.fieldValueFactorFunction("boost"));
     }
     }
 
 
-    private void assertQuery(QueryBuilder query1, QueryBuilder query2) {
-        assertEquals(query1.toString(), query2.toString());
+    private void assertQuery(QueryBuilder query1, QueryContext context) {
+        assertQuery(query1, null, null, context);
+    }
+
+    private void assertQuery(QueryBuilder query1, Map<String, List<String>> fieldLogMap, Set<String> highlightedQuerySet,
+            QueryContext context) {
+        assertEquals(query1.toString(), context.getQueryBuilder().toString());
+        assertFieldLogs(fieldLogMap);
+        assertHighlightedQueries(highlightedQuerySet);
+    }
+
+    private void assertQueryContext(String query, QueryContext context) {
+        assertQueryContext(query, null, null, context);
+    }
+
+    private void assertQueryContext(String query, Map<String, List<String>> fieldLogMap, Set<String> highlightedQuerySet,
+            QueryContext context) {
+        assertQueryContext(query, fieldLogMap, highlightedQuerySet, null, context);
+    }
+
+    private void assertQueryContext(String query, Map<String, List<String>> fieldLogMap, Set<String> highlightedQuerySet, String sorts,
+            QueryContext context) {
+        assertEquals(query, context.getQueryBuilder().toString().replaceAll("\\s", ""));
+        assertFieldLogs(fieldLogMap);
+        assertHighlightedQueries(highlightedQuerySet);
+        if (sorts != null) {
+            assertEquals(sorts, context.sortBuilders().stream().map(s -> s.toString().replaceAll("\\s", "")).collect(Collectors.joining()));
+        }
+    }
+
+    private void assertHighlightedQueries(Set<String> highlightedQuerySet) {
+        @SuppressWarnings("unchecked")
+        Set<String> set = (Set<String>) getMockRequest().getAttribute(Constants.HIGHLIGHT_QUERIES);
+        if (highlightedQuerySet != null) {
+            assertEquals(highlightedQuerySet.stream().sorted().collect(Collectors.joining("\n")),
+                    set.stream().sorted().collect(Collectors.joining("\n")));
+        }
+        set.clear();
+    }
+
+    private void assertFieldLogs(Map<String, List<String>> fieldLogMap) {
+        @SuppressWarnings("unchecked")
+        Map<String, List<String>> map = (Map<String, List<String>>) getMockRequest().getAttribute(Constants.FIELD_LOGS);
+        if (fieldLogMap != null) {
+            assertEquals(fieldLogMap, map);
+        }
+        map.clear();
     }
     }
 
 
-    private QueryBuilder buildQuery(String query) {
+    private QueryContext buildQuery(String query) {
         return queryHelper.build(SearchRequestType.SEARCH, query, context -> {
         return queryHelper.build(SearchRequestType.SEARCH, query, context -> {
             context.skipRoleQuery();
             context.skipRoleQuery();
-        }).getQueryBuilder();
+        });
     }
     }
 
 
 }
 }