This is an automated email from the ASF dual-hosted git repository.

ronny pushed a commit to branch nouveau4win
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 8f31978842919a196bb0b4b593f952cb1a187835
Author: Robert Newson <[email protected]>
AuthorDate: Thu Jul 20 22:39:47 2023 +0100

    allow locale setting for number detection
---
 .../org/apache/couchdb/nouveau/api/SearchRequest.java | 16 ++++++++++++++--
 .../apache/couchdb/nouveau/lucene9/Lucene9Index.java  |  2 +-
 .../couchdb/nouveau/lucene9/NouveauQueryParser.java   | 17 ++++++++++++-----
 .../nouveau/lucene9/NouveauQueryParserTest.java       | 15 ++++++++++++++-
 src/docs/src/api/ddoc/nouveau.rst                     |  3 +++
 src/nouveau/src/nouveau_httpd.erl                     |  6 ++++++
 test/elixir/test/config/nouveau.elixir                |  1 +
 test/elixir/test/nouveau_test.exs                     | 19 +++++++++++++++++++
 8 files changed, 70 insertions(+), 9 deletions(-)

diff --git 
a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java 
b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
index 09f2818aa..2fc9e1f69 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
@@ -22,6 +22,7 @@ import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Positive;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import org.apache.couchdb.nouveau.core.ser.PrimitiveWrapper;
 
@@ -31,6 +32,8 @@ public class SearchRequest {
     @NotNull
     private String query;
 
+    private Locale locale;
+
     private String partition;
 
     @Positive
@@ -61,6 +64,15 @@ public class SearchRequest {
         return query;
     }
 
+    public void setLocale(final Locale locale) {
+        this.locale = locale;
+    }
+
+    @JsonProperty
+    public Locale getLocale() {
+        return locale;
+    }
+
     public void setPartition(final String partition) {
         this.partition = partition;
     }
@@ -142,7 +154,7 @@ public class SearchRequest {
 
     @Override
     public String toString() {
-        return "SearchRequest [query=" + query + ", sort=" + sort + ", limit=" 
+ limit + ", after=" + after
-                + ", counts=" + counts + ", ranges=" + ranges + "]";
+        return "SearchRequest [query=" + query + ", locale=" + locale + ", 
sort=" + sort + ", limit=" + limit
+                + ", after=" + after + ", counts=" + counts + ", ranges=" + 
ranges + "]";
     }
 }
diff --git 
a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java 
b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
index 0de610f84..69e6b1264 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
@@ -489,7 +489,7 @@ public class Lucene9Index extends Index {
     }
 
     private Query parse(final SearchRequest request) {
-        var queryParser = new NouveauQueryParser(analyzer);
+        var queryParser = new NouveauQueryParser(analyzer, 
request.getLocale());
         Query result;
         try {
             result = queryParser.parse(request.getQuery(), "default");
diff --git 
a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
 
b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
index 6516efc2c..f1cf60f0f 100644
--- 
a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
+++ 
b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
@@ -16,6 +16,7 @@ package org.apache.couchdb.nouveau.lucene9;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.util.List;
+import java.util.Locale;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
 import org.apache.lucene.queryparser.flexible.core.QueryParserHelper;
@@ -55,11 +56,11 @@ import org.apache.lucene.search.Query;
 
 public final class NouveauQueryParser extends QueryParserHelper {
 
-    public NouveauQueryParser(final Analyzer analyzer) {
+    public NouveauQueryParser(final Analyzer analyzer, final Locale locale) {
         super(
                 new StandardQueryConfigHandler(),
                 new StandardSyntaxParser(),
-                new NouveauQueryNodeProcessorPipeline(),
+                new NouveauQueryNodeProcessorPipeline(locale),
                 new StandardQueryTreeBuilder());
         
getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS, true);
         getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer);
@@ -77,7 +78,7 @@ public final class NouveauQueryParser extends 
QueryParserHelper {
      */
     public static class NouveauQueryNodeProcessorPipeline extends 
QueryNodeProcessorPipeline {
 
-        public NouveauQueryNodeProcessorPipeline() {
+        public NouveauQueryNodeProcessorPipeline(final Locale locale) {
             super(null);
             add(new WildcardQueryNodeProcessor());
             add(new MultiFieldQueryNodeProcessor());
@@ -85,7 +86,7 @@ public final class NouveauQueryParser extends 
QueryParserHelper {
             add(new RegexpQueryNodeProcessor());
             add(new MatchAllDocsQueryNodeProcessor());
             add(new OpenRangeQueryNodeProcessor());
-            add(new NouveauPointProcessor());
+            add(new NouveauPointProcessor(locale));
             add(new TermRangeQueryNodeProcessor());
             add(new AllowLeadingWildcardProcessor());
             add(new AnalyzerQueryNodeProcessor());
@@ -107,9 +108,15 @@ public final class NouveauQueryParser extends 
QueryParserHelper {
      */
     public static class NouveauPointProcessor extends QueryNodeProcessorImpl {
 
+        private final Locale locale;
+
+        NouveauPointProcessor(final Locale locale) {
+            this.locale = locale != null ? locale : Locale.getDefault();
+        }
+
         @Override
         protected QueryNode postProcessNode(final QueryNode node) throws 
QueryNodeException {
-            final var numberFormat = NumberFormat.getInstance();
+            final var numberFormat = NumberFormat.getInstance(locale);
             final var pointsConfig = new PointsConfig(numberFormat, 
Double.class);
 
             if (node instanceof FieldQueryNode && !(node.getParent() 
instanceof RangeQueryNode)) {
diff --git 
a/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParserTest.java
 
b/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParserTest.java
index 3b41f4397..6dbcceeb8 100644
--- 
a/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParserTest.java
+++ 
b/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParserTest.java
@@ -15,6 +15,7 @@ package org.apache.couchdb.nouveau.lucene9;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.Locale;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.index.Term;
@@ -34,7 +35,7 @@ public class NouveauQueryParserTest {
 
     @BeforeAll
     public static void setup() {
-        qp = new NouveauQueryParser(new StandardAnalyzer());
+        qp = new NouveauQueryParser(new StandardAnalyzer(), Locale.US);
     }
 
     @Test
@@ -100,4 +101,16 @@ public class NouveauQueryParserTest {
         assertThat(qp.parse("foo:[1.0 TO Infinity]", DEFAULT_FIELD))
                 .isEqualTo(DoublePoint.newRangeQuery("foo", new double[] {1}, 
new double[] {Double.POSITIVE_INFINITY}));
     }
+
+    @Test
+    public void testLocales() throws Exception {
+        var us = new NouveauQueryParser(new StandardAnalyzer(), Locale.US);
+        var de = new NouveauQueryParser(new StandardAnalyzer(), Locale.GERMAN);
+
+        assertThat(us.parse("foo:[10.0 TO 20.0]", DEFAULT_FIELD))
+                .isEqualTo(DoublePoint.newRangeQuery("foo", new double[] {10}, 
new double[] {20}));
+
+        assertThat(de.parse("foo:[10.0 TO 20.0]", DEFAULT_FIELD))
+                .isEqualTo(DoublePoint.newRangeQuery("foo", new double[] 
{100}, new double[] {200}));
+    }
 }
diff --git a/src/docs/src/api/ddoc/nouveau.rst 
b/src/docs/src/api/ddoc/nouveau.rst
index fb6731a5b..0cd40bb5d 100644
--- a/src/docs/src/api/ddoc/nouveau.rst
+++ b/src/docs/src/api/ddoc/nouveau.rst
@@ -46,6 +46,9 @@
         name among the documents that match the search query.
     :query boolean include_docs: Include the full content of the documents in 
the
         response.
+    :query string locale: The (Java) locale used to parse numbers in range 
queries.
+        Defaults to the JDK default locale if not specified. Some examples are 
``de``
+        , ``us``, ``gb``.
     :query number limit: Limit the number of the returned documents to the 
specified
         number. For a grouped search, this parameter limits the number of 
documents per
         group.
diff --git a/src/nouveau/src/nouveau_httpd.erl 
b/src/nouveau/src/nouveau_httpd.erl
index e60d7e440..10917b3d4 100644
--- a/src/nouveau/src/nouveau_httpd.erl
+++ b/src/nouveau/src/nouveau_httpd.erl
@@ -65,6 +65,7 @@ handle_search_req_int(#httpd{method = 'GET', path_parts = [_, 
_, _, _, IndexName
     DbName = couch_db:name(Db),
     QueryArgs = validate_query_args(#{
         query => chttpd:qs_value(Req, "q"),
+        locale => chttpd:qs_value(Req, "locale"),
         partition => chttpd:qs_value(Req, "partition"),
         limit => chttpd:qs_value(Req, "limit"),
         sort => chttpd:qs_value(Req, "sort"),
@@ -83,6 +84,7 @@ handle_search_req_int(
     ReqBody = chttpd:json_body(Req, [return_maps]),
     QueryArgs = validate_query_args(#{
         query => maps:get(<<"q">>, ReqBody, undefined),
+        locale => maps:get(<<"locale">>, ReqBody, undefined),
         partition => chttpd:qs_value(Req, "partition"),
         limit => maps:get(<<"limit">>, ReqBody, undefined),
         sort => json_or_undefined(<<"sort">>, ReqBody),
@@ -177,6 +179,10 @@ validate_query_arg(query, undefined) ->
     throw({query_parse_error, <<"q parameter is mandatory">>});
 validate_query_arg(query, Val) when is_list(Val); is_binary(Val) ->
     couch_util:to_binary(Val);
+validate_query_arg(locale, undefined) ->
+    null;
+validate_query_arg(locale, Val) when is_list(Val); is_binary(Val) ->
+    couch_util:to_binary(Val);
 validate_query_arg(partition, undefined) ->
     null;
 validate_query_arg(partition, Val) when is_list(Val); is_binary(Val) ->
diff --git a/test/elixir/test/config/nouveau.elixir 
b/test/elixir/test/config/nouveau.elixir
index a580581e6..733aa9ddb 100644
--- a/test/elixir/test/config/nouveau.elixir
+++ b/test/elixir/test/config/nouveau.elixir
@@ -6,6 +6,7 @@
     "search returns all items for POST",
     "search returns all items (paginated)",
     "search for foo:bar",
+    "search for numeric ranges with locales",
     "sort by string field (asc)",
     "sort by string field (desc)",
     "sort by numeric field (asc)",
diff --git a/test/elixir/test/nouveau_test.exs 
b/test/elixir/test/nouveau_test.exs
index 2a8bc513f..28cc27378 100644
--- a/test/elixir/test/nouveau_test.exs
+++ b/test/elixir/test/nouveau_test.exs
@@ -182,6 +182,25 @@ defmodule NouveauTest do
     assert ids == ["doc3"]
   end
 
+  @tag :with_db
+  test "search for numeric ranges with locales", 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.post(url, body: %{q: "bar:[10.0 TO 20.0]", locale: "us", 
include_docs: true})
+    assert_status_code(resp, 200)
+    ids = get_ids(resp)
+    assert ids == ["doc3"]
+
+    url = "/#{db_name}/_design/foo/_nouveau/bar"
+    resp = Couch.post(url, body: %{q: "bar:[10.0 TO 20.0]", locale: "de", 
include_docs: true})
+    assert_status_code(resp, 200)
+    ids = get_ids(resp)
+    assert ids == ["doc2"]
+  end
+
   @tag :with_db
   test "sort by string field (asc)", context do
     db_name = context[:db_name]

Reply via email to