Make _stats accept user-generated aggregates Sometimes the user already has aggregate data that needs to be merged. This patch supports that use case by allowing the user to emit a JSON Object from the map phase containing 'sum', 'count', 'min', 'max', and 'sumsqr' keys. Users can freely intermix raw values and precomputed aggregates in a single view.
BugzID: 14286 Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/ead76caa Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/ead76caa Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/ead76caa Branch: refs/heads/import Commit: ead76caa077f50b4abbaeeee078c2507d68eea08 Parents: ced3a69 Author: Adam Kocoloski <[email protected]> Authored: Mon Aug 13 15:06:44 2012 -0400 Committer: Paul J. Davis <[email protected]> Committed: Fri Jan 17 16:44:30 2014 -0800 ---------------------------------------------------------------------- src/couch_query_servers.erl | 55 +++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/ead76caa/src/couch_query_servers.erl ---------------------------------------------------------------------- diff --git a/src/couch_query_servers.erl b/src/couch_query_servers.erl index 984c029..fb7f354 100644 --- a/src/couch_query_servers.erl +++ b/src/couch_query_servers.erl @@ -228,15 +228,24 @@ sum_terms([X|Xs], [Y|Ys]) when is_number(X), is_number(Y) -> sum_terms(_, _) -> throw({invalid_value, <<"builtin _sum function requires map values to be numbers or lists of numbers">>}). -builtin_stats(reduce, []) -> - {[]}; -builtin_stats(reduce, [[_,First]|Rest]) when is_number(First) -> - Stats = lists:foldl(fun([_K,V], {S,C,Mi,Ma,Sq}) when is_number(V) -> - {S+V, C+1, lists:min([Mi, V]), lists:max([Ma, V]), Sq+(V*V)}; - (_, _) -> - throw({invalid_value, - <<"builtin _stats function requires map values to be numbers">>}) - end, {First,1,First,First,First*First}, Rest), + +builtin_stats(reduce, [[_,First]|Rest]) -> + Acc0 = build_initial_accumulator(First), + Stats = lists:foldl(fun + ([_K,V], {S,C,Mi,Ma,Sq}) when is_number(V) -> + {S+V, C+1, erlang:min(Mi,V), erlang:max(Ma,V), Sq+(V*V)}; + ([_K,{PreRed}], {S,C,Mi,Ma,Sq}) when is_list(PreRed) -> + { + S + get_number(sum, PreRed), + C + get_number(count, PreRed), + erlang:min(get_number(min, PreRed), Mi), + erlang:max(get_number(max, PreRed), Ma), + Sq + get_number(sumsqr, PreRed) + }; + ([_K,V], _) -> + Msg = io_lib:format("non-numeric _stats input: ~w", [V]), + throw({invalid_value, iolist_to_binary(Msg)}) + end, Acc0, Rest), {Sum, Cnt, Min, Max, Sqr} = Stats, {[{sum,Sum}, {count,Cnt}, {min,Min}, {max,Max}, {sumsqr,Sqr}]}; @@ -249,6 +258,34 @@ builtin_stats(rereduce, [[_,First]|Rest]) -> {Sum, Cnt, Min, Max, Sqr} = Stats, {[{sum,Sum}, {count,Cnt}, {min,Min}, {max,Max}, {sumsqr,Sqr}]}. +build_initial_accumulator(X) when is_number(X) -> + {X, 1, X, X, X*X}; +build_initial_accumulator({Props}) -> + { + get_number(sum, Props), + get_number(count, Props), + get_number(min, Props), + get_number(max, Props), + get_number(sumsqr, Props) + }; +build_initial_accumulator(Else) -> + Msg = io_lib:format("non-numeric _stats input: ~w", [Else]), + throw({invalid_value, iolist_to_binary(Msg)}). + +get_number(Key, Props) -> + case couch_util:get_value(Key, Props) of + X when is_number(X) -> + X; + undefined -> + Msg = io_lib:format("user _stats input missing required field ~s", + [Key]), + throw({invalid_value, iolist_to_binary(Msg)}); + Else -> + Msg = io_lib:format("non-numeric _stats input received for ~s: ~w", + [Key, Else]), + throw({invalid_value, iolist_to_binary(Msg)}) + end. + % use the function stored in ddoc.validate_doc_update to test an update. validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) -> JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
