diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java index e12ad247142..2033d580851 100644 --- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java +++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java @@ -48,6 +48,9 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.TopFieldCollector; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.util.Version; import org.opengrok.indexer.analysis.AbstractAnalyzer; @@ -66,6 +69,7 @@ import org.opengrok.indexer.util.Statistics; import org.opengrok.indexer.util.TandemPath; import org.opengrok.indexer.web.Prefix; +import org.opengrok.indexer.web.SortOrder; /** * This is an encapsulation of the details on how to search in the index database. @@ -114,6 +118,10 @@ public class SearchEngine { * Holds value of property type. */ private String type; + /** + * Holds value of property sort. + */ + private SortOrder sortOrder; /** * Holds value of property indexDatabase. */ @@ -181,6 +189,10 @@ private void searchSingleDatabase(boolean paging) throws IOException { SuperIndexSearcher superIndexSearcher = RuntimeEnvironment.getInstance().getSuperIndexSearcher(""); searcherList.add(superIndexSearcher); searcher = superIndexSearcher; + // If a field-based sort is requested, collect all hits (disable paging optimization) + if (sortOrder != SortOrder.RELEVANCY) { + paging = false; + } searchIndex(superIndexSearcher, paging); } @@ -205,18 +217,42 @@ private void searchMultiDatabase(List projectList, boolean paging) thro } private void searchIndex(IndexSearcher searcher, boolean paging) throws IOException { - collector = TopScoreDocCollector.create(hitsPerPage * cachePages, Short.MAX_VALUE); + // Determine if we need a field-based sort or relevancy (score) sort. + Sort luceneSort = null; + if (getSortOrder() == SortOrder.LASTMODIFIED) { + // Descending by date (newest first) - QueryBuilder.DATE is string sortable in index. + luceneSort = new Sort(new SortField(QueryBuilder.DATE, SortField.Type.STRING, true)); + } else if (getSortOrder() == SortOrder.BY_PATH) { + luceneSort = new Sort(new SortField(QueryBuilder.FULLPATH, SortField.Type.STRING)); + } + Statistics stat = new Statistics(); - searcher.search(query, collector); - totalHits = collector.getTotalHits(); - stat.report(LOGGER, Level.FINEST, "search via SearchEngine done", - "search.latency", new String[]{"category", "engine", - "outcome", totalHits > 0 ? "success" : "empty"}); - if (!paging && totalHits > 0) { - collector = TopScoreDocCollector.create(totalHits, Short.MAX_VALUE); + if (luceneSort == null) { + collector = TopScoreDocCollector.create(hitsPerPage * cachePages, Short.MAX_VALUE); searcher.search(query, collector); + totalHits = collector.getTotalHits(); + stat.report(LOGGER, Level.FINEST, "search via SearchEngine done", + "search.latency", new String[]{"category", "engine", + "outcome", totalHits > 0 ? "success" : "empty"}); + if (!paging && totalHits > 0) { + collector = TopScoreDocCollector.create(totalHits, Short.MAX_VALUE); + searcher.search(query, collector); + } + hits = collector.topDocs().scoreDocs; + } else { + // Field based sort; use TopFieldCollector + TopFieldCollector fieldCollector = TopFieldCollector.create(luceneSort, hitsPerPage * cachePages, Short.MAX_VALUE); + searcher.search(query, fieldCollector); + totalHits = fieldCollector.getTotalHits(); + stat.report(LOGGER, Level.FINEST, "search via SearchEngine done (sorted)", + "search.latency", new String[]{"category", "engine", + "outcome", totalHits > 0 ? "success" : "empty"}); + if (!paging && totalHits > 0) { + fieldCollector = TopFieldCollector.create(luceneSort, totalHits, Short.MAX_VALUE); + searcher.search(query, fieldCollector); + } + hits = fieldCollector.topDocs().scoreDocs; } - hits = collector.topDocs().scoreDocs; StoredFields storedFields = searcher.storedFields(); for (ScoreDoc hit : hits) { int docId = hit.doc; @@ -645,4 +681,22 @@ public String getType() { public void setType(String fileType) { this.type = fileType; } + + /** + * Getter for property sort. + * + * @return Value of property sortOrder. + */ + public SortOrder getSortOrder() { + return this.sortOrder; + } + + /** + * Setter for property sort. + * + * @param sortOrder New value of property sortOrder. + */ + public void setSortOrder(SortOrder sortOrder) { + this.sortOrder = sortOrder; + } } diff --git a/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SearchController.java b/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SearchController.java index f251c3b8005..d67f359613d 100644 --- a/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SearchController.java +++ b/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SearchController.java @@ -39,6 +39,7 @@ import org.opengrok.indexer.search.Hit; import org.opengrok.indexer.search.SearchEngine; import org.opengrok.indexer.web.QueryParameters; +import org.opengrok.indexer.web.SortOrder; import org.opengrok.web.PageConfig; import org.opengrok.web.api.v1.filter.CorsEnable; import org.opengrok.web.api.v1.suggester.provider.service.SuggesterService; @@ -81,9 +82,10 @@ public SearchResult search( @QueryParam("projects") final List projects, @QueryParam("maxresults") // Akin to QueryParameters.COUNT_PARAM @DefaultValue(MAX_RESULTS + "") final int maxResults, + @QueryParam(QueryParameters.SORT_PARAM) @DefaultValue("relevancy") final String sort, @QueryParam(QueryParameters.START_PARAM) @DefaultValue(0 + "") final int startDocIndex ) { - try (SearchEngineWrapper engine = new SearchEngineWrapper(full, def, symbol, path, hist, type)) { + try (SearchEngineWrapper engine = new SearchEngineWrapper(full, def, symbol, path, hist, type, SortOrder.get(sort))) { if (!engine.isValid()) { throw new WebApplicationException("Invalid request", Response.Status.BAD_REQUEST); @@ -119,7 +121,8 @@ private SearchEngineWrapper( final String symbol, final String path, final String hist, - final String type + final String type, + final SortOrder sortOrder ) { engine.setFreetext(full); engine.setDefinition(def); @@ -127,6 +130,7 @@ private SearchEngineWrapper( engine.setFile(path); engine.setHistory(hist); engine.setType(type); + engine.setSortOrder(sortOrder); } public List search(