This is an automated email from the ASF dual-hosted git repository. rnewson pushed a commit to branch nouveau-6075-sort-by-relevance in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit bb44851b6da0d664568f151e4fad1a7fb58a723c Author: Robert Newson <[email protected]> AuthorDate: Tue Jul 28 13:30:18 2026 +0100 Fix explicit sorting by relevance/score Also fixed some misleading docs (from the original import from previous search docs) Closes https://github.com/apache/couchdb/issues/6075 --- .../apache/couchdb/nouveau/lucene/LuceneIndex.java | 6 +++- src/docs/src/api/ddoc/nouveau.rst | 2 +- test/elixir/test/config/nouveau.elixir | 4 ++- test/elixir/test/nouveau_test.exs | 34 +++++++++++++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/extra/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene/LuceneIndex.java b/extra/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene/LuceneIndex.java index 14cc672e9..1384e75e7 100644 --- a/extra/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene/LuceneIndex.java +++ b/extra/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene/LuceneIndex.java @@ -102,6 +102,7 @@ public class LuceneIndex extends Index { private static final Sort DEFAULT_SORT = new Sort(SortField.FIELD_SCORE, new SortField("_id", SortField.Type.STRING)); private static final Pattern SORT_FIELD_RE = Pattern.compile("^([-+])?([\\.\\w]+)$"); + private static final SortField FIELD_REVERSE_SCORE = new SortField(null, SortField.Type.SCORE, true); private final Analyzer analyzer; private final IndexWriter writer; @@ -380,9 +381,12 @@ public class LuceneIndex extends Index { } private SortField convertSortField(final String sortString) { - if ("relevance".equals(sortString)) { + if ("-<score>".equals(sortString)) { return SortField.FIELD_SCORE; } + if ("<score>".equals(sortString)) { + return FIELD_REVERSE_SCORE; + } final Matcher m = SORT_FIELD_RE.matcher(sortString); if (!m.matches()) { throw new WebApplicationException(sortString + " is not a valid sort parameter", Status.BAD_REQUEST); diff --git a/src/docs/src/api/ddoc/nouveau.rst b/src/docs/src/api/ddoc/nouveau.rst index b6c262c6d..fa9935e97 100644 --- a/src/docs/src/api/ddoc/nouveau.rst +++ b/src/docs/src/api/ddoc/nouveau.rst @@ -65,7 +65,7 @@ fieldname is the name of a string or double field. You can use a single string to sort by one field or an array of strings to sort by several fields in the same order as the array. - Some examples are ``"relevance"``, ``"bar"``, ``"-foo"`` and + Some examples are ``"-<score>"``, ``"bar"``, ``"-foo"`` and [``"-foo"``, ``"bar"``]. :query number top_n: Limit the number of facets returned by group, defaulting to 10 with a maximum of 1000. diff --git a/test/elixir/test/config/nouveau.elixir b/test/elixir/test/config/nouveau.elixir index 0066ce372..7fd6ff195 100644 --- a/test/elixir/test/config/nouveau.elixir +++ b/test/elixir/test/config/nouveau.elixir @@ -6,7 +6,9 @@ "search returns all items for GET", "search returns all items for POST", "search returns all items (paginated)", - "search returns all matches for hello by relevance", + "search returns all matches for hello by relevance (implicit)", + "search returns all matches for hello by relevance (explicit)", + "search returns all matches for hello by reverse relevance (explicit)", "search for foo:bar", "search for numeric ranges with locales", "multiple values for stored field", diff --git a/test/elixir/test/nouveau_test.exs b/test/elixir/test/nouveau_test.exs index 352270c22..609877b7b 100644 --- a/test/elixir/test/nouveau_test.exs +++ b/test/elixir/test/nouveau_test.exs @@ -194,7 +194,7 @@ defmodule NouveauTest do end @tag :with_db - test "search returns all matches for hello by relevance", context do + test "search returns all matches for hello by relevance (implicit)", context do db_name = context[:db_name] create_search_docs(db_name) create_ddoc(db_name) @@ -209,6 +209,38 @@ defmodule NouveauTest do assert Enum.at(Enum.at(orders, 0), 0)["value"] > Enum.at(Enum.at(orders, 1), 0)["value"] end + @tag :with_db + test "search returns all matches for hello by relevance (explicit)", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/foo/_nouveau/bar" + resp = Couch.get(url, query: %{q: "txt:hello", sort: "\"-<score>\"", include_docs: true}) + assert_status_code(resp, 200) + ids = get_ids(resp) + orders = get_orders(resp) + # doc4 scores higher (more hello's) + assert ids == ["doc4", "doc3"] + assert Enum.at(Enum.at(orders, 0), 0)["value"] > Enum.at(Enum.at(orders, 1), 0)["value"] + end + + @tag :with_db + test "search returns all matches for hello by reverse relevance (explicit)", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/foo/_nouveau/bar" + resp = Couch.get(url, query: %{q: "txt:hello", sort: "\"<score>\"", include_docs: true}) + assert_status_code(resp, 200) + ids = get_ids(resp) + orders = get_orders(resp) + # doc4 scores lower (fewer hello's) + assert ids == ["doc3", "doc4"] + assert Enum.at(Enum.at(orders, 0), 0)["value"] < Enum.at(Enum.at(orders, 1), 0)["value"] + end + @tag :with_db test "search returns all items for POST", context do db_name = context[:db_name]
