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

iilyak pushed a commit to branch couch-stats-resource-tracker-v3-rebase-http-2
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 568baf186fb912180c0df3bb5b8af6328efc2989
Author: ILYA Khlopotov <iil...@apache.org>
AuthorDate: Fri Jun 27 15:46:39 2025 -0700

    Fix encoding of JSON reply
---
 .../src/couch_stats_resource_tracker.hrl           |   5 +-
 src/couch_stats/src/csrt_httpd.erl                 | 111 +++++++++++++++++----
 2 files changed, 93 insertions(+), 23 deletions(-)

diff --git a/src/couch_stats/src/couch_stats_resource_tracker.hrl 
b/src/couch_stats/src/couch_stats_resource_tracker.hrl
index aba38c16e..9b52826c7 100644
--- a/src/couch_stats/src/couch_stats_resource_tracker.hrl
+++ b/src/couch_stats/src/couch_stats_resource_tracker.hrl
@@ -194,4 +194,7 @@
 -type query_options() :: #{limit => pos_integer()}.
 -type aggregation_key() :: tuple_of_field_names().
 -type aggregation_values() :: tuple_of_field_values().
--type query_result() :: #{aggregation_key() => non_neg_integer()}.
+-type aggregation_result() :: #{aggregation_key() => non_neg_integer()}.
+-type ordered_result() :: [{aggregation_key(), non_neg_integer()}].
+-type query_result() :: aggregation_result() | ordered_result().
+-type json_spec(_Spec) :: term().
diff --git a/src/couch_stats/src/csrt_httpd.erl 
b/src/couch_stats/src/csrt_httpd.erl
index 3fb4f0212..562e1a450 100644
--- a/src/couch_stats/src/csrt_httpd.erl
+++ b/src/couch_stats/src/csrt_httpd.erl
@@ -37,7 +37,9 @@ resp_to_json([], Acc) ->
 %     %% TODO: incorporate Bad responses
 %     Resp = rpc_to_json(csrt:rpc(active, [json])),
 %     send_json(Req, Resp);
-handle_resource_status_req(#httpd{method = 'POST', path_parts = 
[<<"_active_resources">>, <<"_match">>, MatcherName]} = Req) ->
+handle_resource_status_req(
+    #httpd{method = 'POST', path_parts = [<<"_active_resources">>, 
<<"_match">>, MatcherName]} = Req
+) ->
     chttpd:validate_ctype(Req, "application/json"),
     {JsonProps} = chttpd:json_body_obj(Req),
     GroupBy = couch_util:get_value(<<"group_by">>, JsonProps),
@@ -46,11 +48,11 @@ handle_resource_status_req(#httpd{method = 'POST', 
path_parts = [<<"_active_reso
 
     case {GroupBy, SortBy, CountBy} of
         {undefined, undefined, {Query}} ->
-            handle_count_by(Req, MatcherName, Query);
+            handle_count_by(Req, binary_to_list(MatcherName), Query);
         {undefined, {Query}, undefined} ->
-            handle_sort_by(Req, MatcherName, Query);
+            handle_sort_by(Req, binary_to_list(MatcherName), Query);
         {{Query}, undefined, undefined} ->
-            handle_group_by(Req, MatcherName, Query);
+            handle_group_by(Req, binary_to_list(MatcherName), Query);
         {_, _, _} ->
             throw({bad_request, <<"Multiple aggregations are not supported">>})
     end;
@@ -66,10 +68,9 @@ handle_count_by(Req, MatcherName, CountBy) ->
     AggregationKeys = couch_util:get_value(<<"aggregate_keys">>, CountBy),
     case csrt:query_count_by(MatcherName, AggregationKeys) of
         {ok, Map} ->
-            send_json(Req, {aggregation_result_to_json(Map)});
-        Else ->
-            %% TODO handle error
-            throw({bad_request, Else})
+            send_json(Req, aggregation_result_to_json(AggregationKeys, Map));
+        {error, Reason} ->
+            send_error(Req, Reason)
     end.
 
 handle_sort_by(Req, MatcherName, SortBy) ->
@@ -77,10 +78,10 @@ handle_sort_by(Req, MatcherName, SortBy) ->
     CounterKey = couch_util:get_value(<<"counter_key">>, SortBy),
     case csrt:query_sort_by(MatcherName, AggregationKeys, CounterKey) of
         {ok, Map} ->
-            send_json(Req, {aggregation_result_to_json(Map)});
-        Else ->
-            %% TODO handle error
-            throw({bad_request, Else})
+            io:format(user, "Map ~p~n", [Map]),
+            send_json(Req, aggregation_result_to_json(AggregationKeys, Map));
+        {error, Reason} ->
+            send_error(Req, Reason)
     end.
 
 handle_group_by(Req, MatcherName, GroupBy) ->
@@ -88,16 +89,82 @@ handle_group_by(Req, MatcherName, GroupBy) ->
     CounterKey = couch_util:get_value(<<"counter_key">>, GroupBy),
     case csrt:query_group_by(MatcherName, AggregationKeys, CounterKey) of
         {ok, Map} ->
-            send_json(Req, {aggregation_result_to_json(Map)});
-        Else ->
-            %% TODO handle error
-            throw({bad_request, Else})
+            send_json(Req, aggregation_result_to_json(AggregationKeys, Map));
+        {error, Reason} ->
+            send_error(Req, Reason)
     end.
 
-aggregation_result_to_json(Map) when is_map(Map) ->
-    maps:fold(fun(K, V, Acc) -> [{key_to_string(K), V} | Acc] end, [], Map).
+%% [{{<<"user_foo">>,<<"eunit-test-db-3950c6fc68955a4b629cebbece5bdfac">>}, 
10}]
+% -type aggregation_result() :: #{aggregation_key() => non_neg_integer()}.
+% -type ordered_result() :: [{aggregation_key(), non_neg_integer()}].
+% -type query_result() :: aggregation_result() | ordered_result().
 
-key_to_string(Key) when is_tuple(Key) ->
-    list_to_binary(string:join([atom_to_list(K) || K <- tuple_to_list(Key)], 
","));
-key_to_string(Key) when is_atom(Key) ->
-    atom_to_binary(Key).
+encode_key(AggregationKeys, Key) ->
+    maps:from_list(lists:zip(AggregationKeys, tuple_to_list(Key))).
+
+-spec aggregation_result_to_json(AggregationKeys :: binary() | [binary()], Map 
:: query_result()) ->
+    json_spec(#{
+        value => non_neg_integer(),
+        key => #{
+            username => string(),
+            dbname => string()
+        }
+    }).
+
+aggregation_result_to_json(AggregationKeys, Map) when
+    is_map(Map) andalso is_list(AggregationKeys)
+->
+    maps:fold(
+        fun(K, V, Acc) ->
+            [
+                #{
+                    value => V,
+                    key => encode_key(AggregationKeys, K)
+                }
+                | Acc
+            ]
+        end,
+        [],
+        Map
+    );
+aggregation_result_to_json(AggregationKey, Map) when
+    is_map(Map) andalso is_binary(AggregationKey)
+->
+    maps:fold(
+        fun(K, V, Acc) ->
+            [
+                #{value => V, key => #{AggregationKey => K}} | Acc
+            ]
+        end,
+        [],
+        Map
+    );
+aggregation_result_to_json(AggregationKeys, Ordered) when
+    is_list(Ordered) andalso is_list(AggregationKeys)
+->
+    lists:map(
+        fun({K, V}) ->
+            #{
+                value => V,
+                key => encode_key(AggregationKeys, K)
+            }
+        end,
+        Ordered
+    );
+aggregation_result_to_json(AggregationKey, Ordered) when
+    is_list(Ordered) andalso is_binary(AggregationKey)
+->
+    lists:map(
+        fun({K, V}) ->
+            #{value => V, key => #{AggregationKey => K}}
+        end,
+        Ordered
+    ).
+
+send_error(Req, {unknown_matcher, Matcher}) ->
+    MatcherBin = list_to_binary(Matcher),
+    chttpd:send_error(Req, {bad_request, <<"Unknown matcher '", 
MatcherBin/binary, "'">>});
+send_error(Req, {invalid_key, FieldName}) ->
+    chttpd:send_error(Req, {bad_request, <<"Unknown field name '", 
FieldName/binary, "'">>});
+send_error(Req, Reason) ->
+    chttpd:send_error(Req, {error, Reason}).
\ No newline at end of file

Reply via email to