Переглянути джерело

fix #2085 add query.track.total.hits

Shinsuke Sugaya 6 роки тому
батько
коміт
66420017eb
26 змінених файлів з 504 додано та 8 видалено
  1. 3 0
      src/main/config/es/fess_log_search_log.json
  2. 4 0
      src/main/java/org/codelibs/fess/api/json/JsonApiManager.java
  3. 1 0
      src/main/java/org/codelibs/fess/app/service/SearchService.java
  4. 1 0
      src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java
  5. 2 0
      src/main/java/org/codelibs/fess/app/web/api/ApiResult.java
  6. 1 0
      src/main/java/org/codelibs/fess/app/web/search/SearchAction.java
  7. 10 0
      src/main/java/org/codelibs/fess/entity/SearchRenderData.java
  8. 7 0
      src/main/java/org/codelibs/fess/es/client/FessEsClient.java
  9. 1 0
      src/main/java/org/codelibs/fess/es/log/bsbhv/BsSearchLogBhv.java
  10. 17 0
      src/main/java/org/codelibs/fess/es/log/bsentity/BsSearchLog.java
  11. 9 0
      src/main/java/org/codelibs/fess/es/log/bsentity/dbmeta/SearchLogDbm.java
  12. 4 0
      src/main/java/org/codelibs/fess/es/log/cbean/bs/BsSearchLogCB.java
  13. 131 0
      src/main/java/org/codelibs/fess/es/log/cbean/ca/bs/BsSearchLogCA.java
  14. 222 0
      src/main/java/org/codelibs/fess/es/log/cbean/cq/bs/BsSearchLogCQ.java
  15. 1 0
      src/main/java/org/codelibs/fess/helper/SearchLogHelper.java
  16. 3 0
      src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
  17. 27 0
      src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
  18. 24 0
      src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java
  19. 7 0
      src/main/java/org/codelibs/fess/util/QueryResponseList.java
  20. 1 0
      src/main/resources/fess_config.properties
  21. 3 0
      src/main/resources/fess_indices/fess_log.search_log/search_log.json
  22. 1 0
      src/main/resources/fess_label.properties
  23. 1 0
      src/main/resources/fess_label_en.properties
  24. 1 0
      src/main/resources/fess_label_ja.properties
  25. 11 4
      src/main/webapp/WEB-INF/view/admin/searchlist/admin_searchlist.jsp
  26. 11 4
      src/main/webapp/WEB-INF/view/searchResults.jsp

+ 3 - 0
src/main/config/es/fess_log_search_log.json

@@ -13,6 +13,9 @@
           "hitCount" : {
             "type" : "long"
           },
+          "hitCountRelation" : {
+            "type" : "keyword"
+          },
           "languages" : {
             "type" : "keyword"
           },

+ 4 - 0
src/main/java/org/codelibs/fess/api/json/JsonApiManager.java

@@ -228,6 +228,7 @@ public class JsonApiManager extends BaseJsonApiManager {
             final String pageSize = Integer.toString(data.getPageSize());
             final String currentPageNumber = Integer.toString(data.getCurrentPageNumber());
             final String allRecordCount = Long.toString(data.getAllRecordCount());
+            final String allRecordCountRelation = data.getAllRecordCountRelation();
             final String allPageCount = Integer.toString(data.getAllPageCount());
             final List<Map<String, Object>> documentItems = data.getDocumentItems();
             final FacetResponse facetResponse = data.getFacetResponse();
@@ -260,6 +261,9 @@ public class JsonApiManager extends BaseJsonApiManager {
             buf.append("\"record_count\":");
             buf.append(allRecordCount);
             buf.append(',');
+            buf.append("\"record_count_relation\":");
+            buf.append(escapeJson(allRecordCountRelation));
+            buf.append(',');
             buf.append("\"page_count\":");
             buf.append(allPageCount);
             buf.append(",\"highlight_params\":");

+ 1 - 0
src/main/java/org/codelibs/fess/app/service/SearchService.java

@@ -166,6 +166,7 @@ public class SearchService {
         data.setPageSize(queryResponseList.getPageSize());
         data.setCurrentPageNumber(queryResponseList.getCurrentPageNumber());
         data.setAllRecordCount(queryResponseList.getAllRecordCount());
+        data.setAllRecordCountRelation(queryResponseList.getAllRecordCountRelation());
         data.setAllPageCount(queryResponseList.getAllPageCount());
         data.setExistNextPage(queryResponseList.isExistNextPage());
         data.setExistPrevPage(queryResponseList.isExistPrevPage());

+ 1 - 0
src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java

@@ -413,6 +413,7 @@ public class AdminSearchlistAction extends FessAdminAction {
             RenderDataUtil.register(data, "pageSize", pageSize);
             RenderDataUtil.register(data, "currentPageNumber", currentPageNumber);
             RenderDataUtil.register(data, "allRecordCount", allRecordCount);
+            RenderDataUtil.register(data, "allRecordCountRelation", allRecordCountRelation);
             RenderDataUtil.register(data, "allPageCount", allPageCount);
             RenderDataUtil.register(data, "existNextPage", existNextPage);
             RenderDataUtil.register(data, "existPrevPage", existPrevPage);

+ 2 - 0
src/main/java/org/codelibs/fess/app/web/api/ApiResult.java

@@ -154,6 +154,7 @@ public class ApiResult {
         protected int pageSize;
         protected int pageNumber;
         protected long recordCount;
+        protected String recordCountRelation;
         protected int pageCount;
         protected boolean nextPage;
         protected boolean prevPage;
@@ -175,6 +176,7 @@ public class ApiResult {
             pageSize = data.getPageSize();
             pageNumber = data.getCurrentPageNumber();
             recordCount = data.getAllRecordCount();
+            recordCountRelation = data.getAllRecordCountRelation();
             pageCount = data.getAllPageCount();
             nextPage = data.isExistNextPage();
             prevPage = data.isExistPrevPage();

+ 1 - 0
src/main/java/org/codelibs/fess/app/web/search/SearchAction.java

@@ -293,6 +293,7 @@ public class SearchAction extends FessSearchAction {
             RenderDataUtil.register(data, "pageSize", pageSize);
             RenderDataUtil.register(data, "currentPageNumber", currentPageNumber);
             RenderDataUtil.register(data, "allRecordCount", allRecordCount);
+            RenderDataUtil.register(data, "allRecordCountRelation", allRecordCountRelation);
             RenderDataUtil.register(data, "allPageCount", allPageCount);
             RenderDataUtil.register(data, "existNextPage", existNextPage);
             RenderDataUtil.register(data, "existPrevPage", existPrevPage);

+ 10 - 0
src/main/java/org/codelibs/fess/entity/SearchRenderData.java

@@ -36,6 +36,8 @@ public class SearchRenderData {
 
     protected long allRecordCount;
 
+    protected String allRecordCountRelation;
+
     protected int allPageCount;
 
     protected boolean existNextPage;
@@ -86,6 +88,10 @@ public class SearchRenderData {
         this.allRecordCount = allRecordCount;
     }
 
+    public void setAllRecordCountRelation(final String allRecordCountRelation) {
+        this.allRecordCountRelation = allRecordCountRelation;
+    }
+
     public void setAllPageCount(final int allPageCount) {
         this.allPageCount = allPageCount;
     }
@@ -158,6 +164,10 @@ public class SearchRenderData {
         return allRecordCount;
     }
 
+    public String getAllRecordCountRelation() {
+        return allRecordCountRelation;
+    }
+
     public int getAllPageCount() {
         return allPageCount;
     }

+ 7 - 0
src/main/java/org/codelibs/fess/es/client/FessEsClient.java

@@ -1076,6 +1076,13 @@ public class FessEsClient implements Client {
 
             searchRequestBuilder.setFrom(offset).setSize(size);
 
+            final Object trackTotalHitsValue = fessConfig.getQueryTrackTotalHitsValue();
+            if (trackTotalHitsValue instanceof Boolean) {
+                searchRequestBuilder.setTrackTotalHits((Boolean) trackTotalHitsValue);
+            } else if (trackTotalHitsValue instanceof Number) {
+                searchRequestBuilder.setTrackTotalHitsUpTo(((Number) trackTotalHitsValue).intValue());
+            }
+
             if (responseFields != null) {
                 searchRequestBuilder.setFetchSource(responseFields, null);
             }

+ 1 - 0
src/main/java/org/codelibs/fess/es/log/bsbhv/BsSearchLogBhv.java

@@ -76,6 +76,7 @@ public abstract class BsSearchLogBhv extends EsAbstractBehavior<SearchLog, Searc
             result.setAccessType(DfTypeUtil.toString(source.get("accessType")));
             result.setClientIp(DfTypeUtil.toString(source.get("clientIp")));
             result.setHitCount(DfTypeUtil.toLong(source.get("hitCount")));
+            result.setHitCountRelation(DfTypeUtil.toString(source.get("hitCountRelation")));
             result.setLanguages(DfTypeUtil.toString(source.get("languages")));
             result.setQueryId(DfTypeUtil.toString(source.get("queryId")));
             result.setQueryOffset(DfTypeUtil.toInteger(source.get("queryOffset")));

+ 17 - 0
src/main/java/org/codelibs/fess/es/log/bsentity/BsSearchLog.java

@@ -46,6 +46,9 @@ public class BsSearchLog extends EsAbstractEntity {
     /** hitCount */
     protected Long hitCount;
 
+    /** hitCountRelation */
+    protected String hitCountRelation;
+
     /** languages */
     protected String languages;
 
@@ -121,6 +124,9 @@ public class BsSearchLog extends EsAbstractEntity {
         if (hitCount != null) {
             addFieldToSource(sourceMap, "hitCount", hitCount);
         }
+        if (hitCountRelation != null) {
+            addFieldToSource(sourceMap, "hitCountRelation", hitCountRelation);
+        }
         if (languages != null) {
             addFieldToSource(sourceMap, "languages", languages);
         }
@@ -182,6 +188,7 @@ public class BsSearchLog extends EsAbstractEntity {
         sb.append(dm).append(accessType);
         sb.append(dm).append(clientIp);
         sb.append(dm).append(hitCount);
+        sb.append(dm).append(hitCountRelation);
         sb.append(dm).append(languages);
         sb.append(dm).append(queryId);
         sb.append(dm).append(queryOffset);
@@ -237,6 +244,16 @@ public class BsSearchLog extends EsAbstractEntity {
         this.hitCount = value;
     }
 
+    public String getHitCountRelation() {
+        checkSpecifiedProperty("hitCountRelation");
+        return convertEmptyToNull(hitCountRelation);
+    }
+
+    public void setHitCountRelation(String value) {
+        registerModifiedProperty("hitCountRelation");
+        this.hitCountRelation = value;
+    }
+
     public String getLanguages() {
         checkSpecifiedProperty("languages");
         return convertEmptyToNull(languages);

+ 9 - 0
src/main/java/org/codelibs/fess/es/log/bsentity/dbmeta/SearchLogDbm.java

@@ -84,6 +84,8 @@ public class SearchLogDbm extends AbstractDBMeta {
         setupEpg(_epgMap, et -> ((SearchLog) et).getClientIp(), (et, vl) -> ((SearchLog) et).setClientIp(DfTypeUtil.toString(vl)),
                 "clientIp");
         setupEpg(_epgMap, et -> ((SearchLog) et).getHitCount(), (et, vl) -> ((SearchLog) et).setHitCount(DfTypeUtil.toLong(vl)), "hitCount");
+        setupEpg(_epgMap, et -> ((SearchLog) et).getHitCountRelation(),
+                (et, vl) -> ((SearchLog) et).setHitCountRelation(DfTypeUtil.toString(vl)), "hitCountRelation");
         setupEpg(_epgMap, et -> ((SearchLog) et).getLanguages(), (et, vl) -> ((SearchLog) et).setLanguages(DfTypeUtil.toString(vl)),
                 "languages");
         setupEpg(_epgMap, et -> ((SearchLog) et).getQueryId(), (et, vl) -> ((SearchLog) et).setQueryId(DfTypeUtil.toString(vl)), "queryId");
@@ -152,6 +154,8 @@ public class SearchLogDbm extends AbstractDBMeta {
             false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false);
     protected final ColumnInfo _columnHitCount = cci("hitCount", "hitCount", null, null, Long.class, "hitCount", null, false, false, false,
             "Long", 0, 0, null, null, false, null, null, null, null, null, false);
+    protected final ColumnInfo _columnHitCountRelation = cci("hitCountRelation", "hitCountRelation", null, null, String.class,
+            "hitCountRelation", null, false, false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false);
     protected final ColumnInfo _columnLanguages = cci("languages", "languages", null, null, String.class, "languages", null, false, false,
             false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false);
     protected final ColumnInfo _columnQueryId = cci("queryId", "queryId", null, null, String.class, "queryId", null, false, false, false,
@@ -195,6 +199,10 @@ public class SearchLogDbm extends AbstractDBMeta {
         return _columnHitCount;
     }
 
+    public ColumnInfo columnHitCountRelation() {
+        return _columnHitCountRelation;
+    }
+
     public ColumnInfo columnLanguages() {
         return _columnLanguages;
     }
@@ -260,6 +268,7 @@ public class SearchLogDbm extends AbstractDBMeta {
         ls.add(columnAccessType());
         ls.add(columnClientIp());
         ls.add(columnHitCount());
+        ls.add(columnHitCountRelation());
         ls.add(columnLanguages());
         ls.add(columnQueryId());
         ls.add(columnQueryOffset());

+ 4 - 0
src/main/java/org/codelibs/fess/es/log/cbean/bs/BsSearchLogCB.java

@@ -188,6 +188,10 @@ public class BsSearchLogCB extends EsAbstractConditionBean {
             doColumn("hitCount");
         }
 
+        public void columnHitCountRelation() {
+            doColumn("hitCountRelation");
+        }
+
         public void columnLanguages() {
             doColumn("languages");
         }

+ 131 - 0
src/main/java/org/codelibs/fess/es/log/cbean/ca/bs/BsSearchLogCA.java

@@ -593,6 +593,137 @@ public abstract class BsSearchLogCA extends EsAbstractConditionAggregation {
         }
     }
 
+    public void setHitCountRelation_Terms() {
+        setHitCountRelation_Terms(null);
+    }
+
+    public void setHitCountRelation_Terms(ConditionOptionCall<TermsAggregationBuilder> opLambda) {
+        setHitCountRelation_Terms("hitCountRelation", opLambda, null);
+    }
+
+    public void setHitCountRelation_Terms(ConditionOptionCall<TermsAggregationBuilder> opLambda, OperatorCall<BsSearchLogCA> aggsLambda) {
+        setHitCountRelation_Terms("hitCountRelation", opLambda, aggsLambda);
+    }
+
+    public void setHitCountRelation_Terms(String name, ConditionOptionCall<TermsAggregationBuilder> opLambda,
+            OperatorCall<BsSearchLogCA> aggsLambda) {
+        TermsAggregationBuilder builder = regTermsA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+        if (aggsLambda != null) {
+            SearchLogCA ca = new SearchLogCA();
+            aggsLambda.callback(ca);
+            ca.getAggregationBuilderList().forEach(builder::subAggregation);
+        }
+    }
+
+    public void setHitCountRelation_SignificantTerms() {
+        setHitCountRelation_SignificantTerms(null);
+    }
+
+    public void setHitCountRelation_SignificantTerms(ConditionOptionCall<SignificantTermsAggregationBuilder> opLambda) {
+        setHitCountRelation_SignificantTerms("hitCountRelation", opLambda, null);
+    }
+
+    public void setHitCountRelation_SignificantTerms(ConditionOptionCall<SignificantTermsAggregationBuilder> opLambda,
+            OperatorCall<BsSearchLogCA> aggsLambda) {
+        setHitCountRelation_SignificantTerms("hitCountRelation", opLambda, aggsLambda);
+    }
+
+    public void setHitCountRelation_SignificantTerms(String name, ConditionOptionCall<SignificantTermsAggregationBuilder> opLambda,
+            OperatorCall<BsSearchLogCA> aggsLambda) {
+        SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+        if (aggsLambda != null) {
+            SearchLogCA ca = new SearchLogCA();
+            aggsLambda.callback(ca);
+            ca.getAggregationBuilderList().forEach(builder::subAggregation);
+        }
+    }
+
+    public void setHitCountRelation_IpRange() {
+        setHitCountRelation_IpRange(null);
+    }
+
+    public void setHitCountRelation_IpRange(ConditionOptionCall<IpRangeAggregationBuilder> opLambda) {
+        setHitCountRelation_IpRange("hitCountRelation", opLambda, null);
+    }
+
+    public void setHitCountRelation_IpRange(ConditionOptionCall<IpRangeAggregationBuilder> opLambda, OperatorCall<BsSearchLogCA> aggsLambda) {
+        setHitCountRelation_IpRange("hitCountRelation", opLambda, aggsLambda);
+    }
+
+    public void setHitCountRelation_IpRange(String name, ConditionOptionCall<IpRangeAggregationBuilder> opLambda,
+            OperatorCall<BsSearchLogCA> aggsLambda) {
+        IpRangeAggregationBuilder builder = regIpRangeA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+        if (aggsLambda != null) {
+            SearchLogCA ca = new SearchLogCA();
+            aggsLambda.callback(ca);
+            ca.getAggregationBuilderList().forEach(builder::subAggregation);
+        }
+    }
+
+    public void setHitCountRelation_Count() {
+        setHitCountRelation_Count(null);
+    }
+
+    public void setHitCountRelation_Count(ConditionOptionCall<ValueCountAggregationBuilder> opLambda) {
+        setHitCountRelation_Count("hitCountRelation", opLambda);
+    }
+
+    public void setHitCountRelation_Count(String name, ConditionOptionCall<ValueCountAggregationBuilder> opLambda) {
+        ValueCountAggregationBuilder builder = regCountA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Cardinality() {
+        setHitCountRelation_Cardinality(null);
+    }
+
+    public void setHitCountRelation_Cardinality(ConditionOptionCall<CardinalityAggregationBuilder> opLambda) {
+        setHitCountRelation_Cardinality("hitCountRelation", opLambda);
+    }
+
+    public void setHitCountRelation_Cardinality(String name, ConditionOptionCall<CardinalityAggregationBuilder> opLambda) {
+        CardinalityAggregationBuilder builder = regCardinalityA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Missing() {
+        setHitCountRelation_Missing(null);
+    }
+
+    public void setHitCountRelation_Missing(ConditionOptionCall<MissingAggregationBuilder> opLambda) {
+        setHitCountRelation_Missing("hitCountRelation", opLambda, null);
+    }
+
+    public void setHitCountRelation_Missing(ConditionOptionCall<MissingAggregationBuilder> opLambda, OperatorCall<BsSearchLogCA> aggsLambda) {
+        setHitCountRelation_Missing("hitCountRelation", opLambda, aggsLambda);
+    }
+
+    public void setHitCountRelation_Missing(String name, ConditionOptionCall<MissingAggregationBuilder> opLambda,
+            OperatorCall<BsSearchLogCA> aggsLambda) {
+        MissingAggregationBuilder builder = regMissingA(name, "hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+        if (aggsLambda != null) {
+            SearchLogCA ca = new SearchLogCA();
+            aggsLambda.callback(ca);
+            ca.getAggregationBuilderList().forEach(builder::subAggregation);
+        }
+    }
+
     public void setLanguages_Terms() {
         setLanguages_Terms(null);
     }

+ 222 - 0
src/main/java/org/codelibs/fess/es/log/cbean/cq/bs/BsSearchLogCQ.java

@@ -807,6 +807,228 @@ public abstract class BsSearchLogCQ extends EsAbstractConditionQuery {
         return this;
     }
 
+    public void setHitCountRelation_Equal(String hitCountRelation) {
+        setHitCountRelation_Term(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Equal(String hitCountRelation, ConditionOptionCall<TermQueryBuilder> opLambda) {
+        setHitCountRelation_Term(hitCountRelation, opLambda);
+    }
+
+    public void setHitCountRelation_Term(String hitCountRelation) {
+        setHitCountRelation_Term(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Term(String hitCountRelation, ConditionOptionCall<TermQueryBuilder> opLambda) {
+        TermQueryBuilder builder = regTermQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_NotEqual(String hitCountRelation) {
+        setHitCountRelation_NotTerm(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_NotTerm(String hitCountRelation) {
+        setHitCountRelation_NotTerm(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_NotEqual(String hitCountRelation, ConditionOptionCall<BoolQueryBuilder> opLambda) {
+        setHitCountRelation_NotTerm(hitCountRelation, opLambda);
+    }
+
+    public void setHitCountRelation_NotTerm(String hitCountRelation, ConditionOptionCall<BoolQueryBuilder> opLambda) {
+        not(not -> not.setHitCountRelation_Term(hitCountRelation), opLambda);
+    }
+
+    public void setHitCountRelation_Terms(Collection<String> hitCountRelationList) {
+        setHitCountRelation_Terms(hitCountRelationList, null);
+    }
+
+    public void setHitCountRelation_Terms(Collection<String> hitCountRelationList, ConditionOptionCall<TermsQueryBuilder> opLambda) {
+        TermsQueryBuilder builder = regTermsQ("hitCountRelation", hitCountRelationList);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_InScope(Collection<String> hitCountRelationList) {
+        setHitCountRelation_Terms(hitCountRelationList, null);
+    }
+
+    public void setHitCountRelation_InScope(Collection<String> hitCountRelationList, ConditionOptionCall<TermsQueryBuilder> opLambda) {
+        setHitCountRelation_Terms(hitCountRelationList, opLambda);
+    }
+
+    public void setHitCountRelation_Match(String hitCountRelation) {
+        setHitCountRelation_Match(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Match(String hitCountRelation, ConditionOptionCall<MatchQueryBuilder> opLambda) {
+        MatchQueryBuilder builder = regMatchQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_MatchPhrase(String hitCountRelation) {
+        setHitCountRelation_MatchPhrase(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_MatchPhrase(String hitCountRelation, ConditionOptionCall<MatchPhraseQueryBuilder> opLambda) {
+        MatchPhraseQueryBuilder builder = regMatchPhraseQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_MatchPhrasePrefix(String hitCountRelation) {
+        setHitCountRelation_MatchPhrasePrefix(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_MatchPhrasePrefix(String hitCountRelation, ConditionOptionCall<MatchPhrasePrefixQueryBuilder> opLambda) {
+        MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Fuzzy(String hitCountRelation) {
+        setHitCountRelation_Fuzzy(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Fuzzy(String hitCountRelation, ConditionOptionCall<MatchQueryBuilder> opLambda) {
+        MatchQueryBuilder builder = regFuzzyQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Prefix(String hitCountRelation) {
+        setHitCountRelation_Prefix(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Prefix(String hitCountRelation, ConditionOptionCall<PrefixQueryBuilder> opLambda) {
+        PrefixQueryBuilder builder = regPrefixQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Wildcard(String hitCountRelation) {
+        setHitCountRelation_Wildcard(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Wildcard(String hitCountRelation, ConditionOptionCall<WildcardQueryBuilder> opLambda) {
+        WildcardQueryBuilder builder = regWildcardQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Regexp(String hitCountRelation) {
+        setHitCountRelation_Regexp(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_Regexp(String hitCountRelation, ConditionOptionCall<RegexpQueryBuilder> opLambda) {
+        RegexpQueryBuilder builder = regRegexpQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_SpanTerm(String hitCountRelation) {
+        setHitCountRelation_SpanTerm("hitCountRelation", null);
+    }
+
+    public void setHitCountRelation_SpanTerm(String hitCountRelation, ConditionOptionCall<SpanTermQueryBuilder> opLambda) {
+        SpanTermQueryBuilder builder = regSpanTermQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_GreaterThan(String hitCountRelation) {
+        setHitCountRelation_GreaterThan(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_GreaterThan(String hitCountRelation, ConditionOptionCall<RangeQueryBuilder> opLambda) {
+        final Object _value = hitCountRelation;
+        RangeQueryBuilder builder = regRangeQ("hitCountRelation", ConditionKey.CK_GREATER_THAN, _value);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_LessThan(String hitCountRelation) {
+        setHitCountRelation_LessThan(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_LessThan(String hitCountRelation, ConditionOptionCall<RangeQueryBuilder> opLambda) {
+        final Object _value = hitCountRelation;
+        RangeQueryBuilder builder = regRangeQ("hitCountRelation", ConditionKey.CK_LESS_THAN, _value);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_GreaterEqual(String hitCountRelation) {
+        setHitCountRelation_GreaterEqual(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_GreaterEqual(String hitCountRelation, ConditionOptionCall<RangeQueryBuilder> opLambda) {
+        final Object _value = hitCountRelation;
+        RangeQueryBuilder builder = regRangeQ("hitCountRelation", ConditionKey.CK_GREATER_EQUAL, _value);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_LessEqual(String hitCountRelation) {
+        setHitCountRelation_LessEqual(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_LessEqual(String hitCountRelation, ConditionOptionCall<RangeQueryBuilder> opLambda) {
+        final Object _value = hitCountRelation;
+        RangeQueryBuilder builder = regRangeQ("hitCountRelation", ConditionKey.CK_LESS_EQUAL, _value);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_Exists() {
+        setHitCountRelation_Exists(null);
+    }
+
+    public void setHitCountRelation_Exists(ConditionOptionCall<ExistsQueryBuilder> opLambda) {
+        ExistsQueryBuilder builder = regExistsQ("hitCountRelation");
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public void setHitCountRelation_CommonTerms(String hitCountRelation) {
+        setHitCountRelation_CommonTerms(hitCountRelation, null);
+    }
+
+    public void setHitCountRelation_CommonTerms(String hitCountRelation, ConditionOptionCall<CommonTermsQueryBuilder> opLambda) {
+        CommonTermsQueryBuilder builder = regCommonTermsQ("hitCountRelation", hitCountRelation);
+        if (opLambda != null) {
+            opLambda.callback(builder);
+        }
+    }
+
+    public BsSearchLogCQ addOrderBy_HitCountRelation_Asc() {
+        regOBA("hitCountRelation");
+        return this;
+    }
+
+    public BsSearchLogCQ addOrderBy_HitCountRelation_Desc() {
+        regOBD("hitCountRelation");
+        return this;
+    }
+
     public void setLanguages_Equal(String languages) {
         setLanguages_Term(languages, null);
     }

+ 1 - 0
src/main/java/org/codelibs/fess/helper/SearchLogHelper.java

@@ -106,6 +106,7 @@ public class SearchLogHelper {
         searchLog.setRoles(roleQueryHelper.build(params.getType()).stream().toArray(n -> new String[n]));
         searchLog.setQueryId(queryId);
         searchLog.setHitCount(queryResponseList.getAllRecordCount());
+        searchLog.setHitCountRelation(queryResponseList.getAllRecordCountRelation());
         searchLog.setResponseTime(queryResponseList.getExecTime());
         searchLog.setQueryTime(queryResponseList.getQueryTime());
         searchLog.setSearchWord(StringUtils.abbreviate(query, 1000));

+ 3 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java

@@ -620,6 +620,9 @@ public class FessLabels extends UserMessages {
     /** The key of the message: Results &lt;b&gt;{2}&lt;/b&gt;&lt;span class="hidden-phone"&gt; -&lt;/span&gt; &lt;b&gt;{3}&lt;/b&gt; of &lt;b&gt;{1}&lt;/b&gt; for &lt;b&gt;{0}&lt;/b&gt; */
     public static final String LABELS_search_result_status = "{labels.search_result_status}";
 
+    /** The key of the message: Results &lt;b&gt;{2}&lt;/b&gt;&lt;span class="hidden-phone"&gt; -&lt;/span&gt; &lt;b&gt;{3}&lt;/b&gt; of about &lt;b&gt;{1}&lt;/b&gt; for &lt;b&gt;{0}&lt;/b&gt; */
+    public static final String LABELS_search_result_status_over = "{labels.search_result_status_over}";
+
     /** The key of the message: ({0} sec) */
     public static final String LABELS_search_result_time = "{labels.search_result_time}";
 

+ 27 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -624,6 +624,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. true */
     String QUERY_TIMEOUT_LOGGING = "query.timeout.logging";
 
+    /** The key of the configuration. e.g. 10000 */
+    String QUERY_TRACK_TOTAL_HITS = "query.track.total.hits";
+
     /** The key of the configuration. e.g. location */
     String QUERY_GEO_FIELDS = "query.geo.fields";
 
@@ -3255,6 +3258,21 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     boolean isQueryTimeoutLogging();
 
+    /**
+     * Get the value for the key 'query.track.total.hits'. <br>
+     * The value is, e.g. 10000 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryTrackTotalHits();
+
+    /**
+     * Get the value for the key 'query.track.total.hits' as {@link Integer}. <br>
+     * The value is, e.g. 10000 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryTrackTotalHitsAsInteger();
+
     /**
      * Get the value for the key 'query.geo.fields'. <br>
      * The value is, e.g. location <br>
@@ -6866,6 +6884,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return is(FessConfig.QUERY_TIMEOUT_LOGGING);
         }
 
+        public String getQueryTrackTotalHits() {
+            return get(FessConfig.QUERY_TRACK_TOTAL_HITS);
+        }
+
+        public Integer getQueryTrackTotalHitsAsInteger() {
+            return getAsInteger(FessConfig.QUERY_TRACK_TOTAL_HITS);
+        }
+
         public String getQueryGeoFields() {
             return get(FessConfig.QUERY_GEO_FIELDS);
         }
@@ -8460,6 +8486,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.QUERY_MAX_LENGTH, "1000");
             defaultMap.put(FessConfig.QUERY_TIMEOUT, "10000");
             defaultMap.put(FessConfig.QUERY_TIMEOUT_LOGGING, "true");
+            defaultMap.put(FessConfig.QUERY_TRACK_TOTAL_HITS, "10000");
             defaultMap.put(FessConfig.QUERY_GEO_FIELDS, "location");
             defaultMap.put(FessConfig.QUERY_BROWSER_LANG_PARAMETER_NAME, "browser_lang");
             defaultMap.put(FessConfig.QUERY_REPLACE_TERM_WITH_PREFIX_QUERY, "true");

+ 24 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -70,6 +70,8 @@ import org.lastaflute.web.validation.theme.typed.LongTypeValidator;
 
 public interface FessProp {
 
+    String QUERY_TRACK_TOTAL_HITS_VALUE = "queryTrackTotalHitsValue";
+
     String CORS_ALLOW_ORIGIN = "CorsAllowOrigin";
 
     String API_DASHBOARD_RESPONSE_HEADER_LIST = "apiDashboardResponseHeaderList";
@@ -1961,4 +1963,26 @@ public interface FessProp {
                 stream -> stream.map(s -> s.trim().toUpperCase(Locale.ENGLISH)).collect(Collectors.toSet()));
     }
 
+    String getQueryTrackTotalHits();
+
+    default Object getQueryTrackTotalHitsValue() {
+        Object value = propMap.get(QUERY_TRACK_TOTAL_HITS_VALUE);
+        if (value == null) {
+            String v = getQueryTrackTotalHits();
+            if (Constants.TRUE.equalsIgnoreCase(v)) {
+                value = Boolean.TRUE;
+            } else if (Constants.FALSE.equalsIgnoreCase(v)) {
+                value = Boolean.FALSE;
+            } else {
+                try {
+                    value = Integer.valueOf(Integer.parseInt(v));
+                } catch (NumberFormatException e) {
+                    value = StringUtil.EMPTY;
+                }
+            }
+            propMap.put(QUERY_TRACK_TOTAL_HITS_VALUE, value);
+        }
+        return value;
+    }
+
 }

+ 7 - 0
src/main/java/org/codelibs/fess/util/QueryResponseList.java

@@ -57,6 +57,8 @@ public class QueryResponseList implements List<Map<String, Object>> {
 
     protected long allRecordCount;
 
+    protected String allRecordCountRelation;
+
     protected int allPageCount;
 
     protected boolean existNextPage;
@@ -93,6 +95,7 @@ public class QueryResponseList implements List<Map<String, Object>> {
             final FessConfig fessConfig = ComponentUtil.getFessConfig();
             final SearchHits searchHits = searchResponse.getHits();
             allRecordCount = searchHits.getTotalHits().value;
+            allRecordCountRelation = searchHits.getTotalHits().relation.toString();
             queryTime = searchResponse.getTook().millis();
 
             if (searchResponse.getTotalShards() != searchResponse.getSuccessfulShards()) {
@@ -362,6 +365,10 @@ public class QueryResponseList implements List<Map<String, Object>> {
         return allRecordCount;
     }
 
+    public String getAllRecordCountRelation() {
+        return allRecordCountRelation;
+    }
+
     public int getAllPageCount() {
         return allPageCount;
     }

+ 1 - 0
src/main/resources/fess_config.properties

@@ -313,6 +313,7 @@ index.indices.timeout=1m
 query.max.length=1000
 query.timeout=10000
 query.timeout.logging=true
+query.track.total.hits=10000
 query.geo.fields=location
 query.browser.lang.parameter.name=browser_lang
 query.replace.term.with.prefix.query=true

+ 3 - 0
src/main/resources/fess_indices/fess_log.search_log/search_log.json

@@ -43,6 +43,9 @@
       "hitCount": {
         "type": "long"
       },
+      "hitCountRelation": {
+        "type": "keyword"
+      },
       "queryOffset": {
         "type": "integer"
       },

+ 1 - 0
src/main/resources/fess_label.properties

@@ -198,6 +198,7 @@ labels.footer.copyright=&copy;2019 <a href="https://github.com/codelibs">CodeLib
 labels.search=Search
 labels.similar_doc_result_status=Similar results are displayed.
 labels.search_result_status=Results <b>{2}</b><span class="hidden-phone"> -</span> <b>{3}</b> of <b>{1}</b> for <b>{0}</b>
+labels.search_result_status_over=Results <b>{2}</b><span class="hidden-phone"> -</span> <b>{3}</b> of about <b>{1}</b> for <b>{0}</b>
 labels.search_result_time=({0} sec)
 labels.prev_page=Prev
 labels.next_page=Next

+ 1 - 0
src/main/resources/fess_label_en.properties

@@ -198,6 +198,7 @@ labels.footer.copyright=&copy;2019 <a href="https://github.com/codelibs">CodeLib
 labels.search=Search
 labels.similar_doc_result_status=Similar results are displayed.
 labels.search_result_status=Results <b>{2}</b><span class="hidden-phone"> -</span> <b>{3}</b> of <b>{1}</b> for <b>{0}</b>
+labels.search_result_status_over=Results <b>{2}</b><span class="hidden-phone"> -</span> <b>{3}</b> of about <b>{1}</b> for <b>{0}</b>
 labels.search_result_time=({0} sec)
 labels.prev_page=Prev
 labels.next_page=Next

+ 1 - 0
src/main/resources/fess_label_ja.properties

@@ -190,6 +190,7 @@ labels.footer.copyright=&copy;2019 <a href="https://github.com/codelibs">CodeLib
 labels.search=検索
 labels.similar_doc_result_status=類似している結果を表示しています。
 labels.search_result_status=<b>{0}</b> の検索結果<span class="br-phone"></span> <b>{1}</b> 件中 <b>{2}</b> - <b>{3}</b> 件目
+labels.search_result_status_over=<b>{0}</b> の検索結果<span class="br-phone"></span> 約 <b>{1}</b> 件中 <b>{2}</b> - <b>{3}</b> 件目
 labels.search_result_time=({0} 秒)
 labels.prev_page=前へ
 labels.next_page=次へ

+ 11 - 4
src/main/webapp/WEB-INF/view/admin/searchlist/admin_searchlist.jsp

@@ -71,10 +71,17 @@
 									<c:when test="${f:h(allRecordCount) > 0}">
 										<div id="subheader" class="row top10">
 											<div class="col-xs-12">
-												<la:message key="labels.search_result_status"
-													arg0="${f:h(q)}" arg1="${f:h(allRecordCount)}"
-													arg2="${f:h(currentStartRecordNumber)}"
-													arg3="${f:h(currentEndRecordNumber)}" />
+												<c:if test="${allRecordCountRelation==null}">
+													<la:message key="labels.search_result_status"
+														arg0="${f:h(q)}" arg1="${f:h(allRecordCount)}"
+														arg2="${f:h(currentStartRecordNumber)}"
+														arg3="${f:h(currentEndRecordNumber)}" />
+												</c:if><c:if test="${allRecordCountRelation!=null}">
+													<la:message key="labels.search_result_status_over"
+														arg0="${f:h(q)}" arg1="${f:h(allRecordCount)}"
+														arg2="${f:h(currentStartRecordNumber)}"
+														arg3="${f:h(currentEndRecordNumber)}" />
+												</c:if>
 												<c:if test="${execTime!=null}">
 													<la:message key="labels.search_result_time"
 														arg0="${f:h(execTime)}" />

+ 11 - 4
src/main/webapp/WEB-INF/view/searchResults.jsp

@@ -3,10 +3,17 @@
 <div id="subheader" class="row">
 	<div class="col">
 		<p>
-			<la:message key="labels.search_result_status"
-				arg0="${displayQuery}" arg1="${f:h(allRecordCount)}"
-				arg2="${f:h(currentStartRecordNumber)}"
-				arg3="${f:h(currentEndRecordNumber)}" />
+			<c:if test="${allRecordCountRelation=='EQUAL_TO'}">
+				<la:message key="labels.search_result_status"
+					arg0="${displayQuery}" arg1="${f:h(allRecordCount)}"
+					arg2="${f:h(currentStartRecordNumber)}"
+					arg3="${f:h(currentEndRecordNumber)}" />
+			</c:if><c:if test="${allRecordCountRelation!='EQUAL_TO'}">
+				<la:message key="labels.search_result_status_over"
+					arg0="${displayQuery}" arg1="${f:h(allRecordCount)}"
+					arg2="${f:h(currentStartRecordNumber)}"
+					arg3="${f:h(currentEndRecordNumber)}" />
+			</c:if>
 			<c:if test="${execTime!=null}">
 				<la:message key="labels.search_result_time" arg0="${f:h(execTime)}" />
 			</c:if>