C0 code coverage information

Generated on 2009-08-09 21:22:55 with etap 0.3.4.

Name Total lines Lines of code Total coverage Code coverage
couch_httpd_view ?? ?? ??
0% 
....1 % Licensed under the Apache License, Version 2.0 (the "License"); you may not
....2 % use this file except in compliance with the License. You may obtain a copy of
....3 % the License at
....4 %
....5 %   http://www.apache.org/licenses/LICENSE-2.0
....6 %
....7 % Unless required by applicable law or agreed to in writing, software
....8 % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
....9 % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
...10 % License for the specific language governing permissions and limitations under
...11 % the License.
...12 
...13 -module(couch_httpd_view).
...14 -include("couch_db.hrl").
...15 
...16 -export([handle_view_req/2,handle_temp_view_req/2,handle_db_view_req/2]).
...17 
...18 -export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]).
...19 -export([make_view_fold_fun/6, finish_view_fold/3, view_row_obj/3]).
...20 -export([view_group_etag/1, view_group_etag/2, make_reduce_fold_funs/5]).
...21 -export([design_doc_view/5, parse_bool_param/1]).
...22 
...23 -import(couch_httpd,
...24     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
...25     start_json_response/2, start_json_response/3, end_json_response/1,
...26     send_chunked_error/2]).
...27 
...28 design_doc_view(Req, Db, Id, ViewName, Keys) ->
...29     DesignId = <<"_design/", Id/binary>>,
...30     Stale = get_stale_type(Req),
...31     Reduce = get_reduce_type(Req),
...32     Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
...33     {ok, View, Group} ->
...34         QueryArgs = parse_view_params(Req, Keys, map),
...35         output_map_view(Req, View, Group, Db, QueryArgs, Keys);
...36     {not_found, Reason} ->
...37         case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
...38         {ok, ReduceView, Group} ->
...39             case Reduce of
...40             false ->
...41                 QueryArgs = parse_view_params(Req, Keys, red_map),
...42                 MapView = couch_view:extract_map_view(ReduceView),
...43                 output_map_view(Req, MapView, Group, Db, QueryArgs, Keys);
...44             _ ->
...45                 QueryArgs = parse_view_params(Req, Keys, reduce),
...46                 output_reduce_view(Req, ReduceView, Group, QueryArgs, Keys)
...47             end;
...48         _ ->
...49             throw({not_found, Reason})
...50         end
...51     end,
...52     couch_stats_collector:increment({httpd, view_reads}),
...53     Result.
...54 
...55 handle_view_req(#httpd{method='GET',
...56         path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) ->
...57     design_doc_view(Req, Db, DName, ViewName, nil);
...58 
...59 handle_view_req(#httpd{method='POST',
...60         path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) ->
...61     {Fields} = couch_httpd:json_body_obj(Req),
...62     case proplists:get_value(<<"keys">>, Fields, nil) of
...63     nil ->
...64         Fmt = "POST to view ~p/~p in database ~p with no keys member.",
...65         ?LOG_DEBUG(Fmt, [DName, ViewName, Db]),
...66         design_doc_view(Req, Db, DName, ViewName, nil);
...67     Keys when is_list(Keys) ->
...68         design_doc_view(Req, Db, DName, ViewName, Keys);
...69     _ ->
...70         throw({bad_request, "`keys` member must be a array."})
...71     end;
...72 
...73 handle_view_req(Req, _Db) ->
...74     send_method_not_allowed(Req, "GET,POST,HEAD").
...75 
...76 handle_db_view_req(#httpd{method='GET',
...77         path_parts=[_Db, _View, DName, ViewName]}=Req, Db) ->
...78     QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil),
...79     #view_query_args{
...80         list = ListName
...81     } = QueryArgs,
...82     ?LOG_DEBUG("ici ~p", [ListName]),
...83     case ListName of
...84         nil -> couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil);
...85         _ ->
...86             couch_httpd_show:handle_view_list(Req, DName, ListName, ViewName, Db, nil)
...87     end;
...88 
...89 handle_db_view_req(#httpd{method='POST',
...90         path_parts=[_Db, _View, DName, ViewName]}=Req, Db) ->
...91     QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil),
...92     #view_query_args{
...93         list = ListName
...94     } = QueryArgs,
...95     case ListName of
...96     nil ->
...97         {Fields} = couch_httpd:json_body_obj(Req),
...98         case proplists:get_value(<<"keys">>, Fields, nil) of
...99         nil ->
..100             Fmt = "POST to view ~p/~p in database ~p with no keys member.",
..101             ?LOG_DEBUG(Fmt, [DName, ViewName, Db]),
..102             couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil);
..103         Keys when is_list(Keys) ->
..104             couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, Keys);
..105         _ ->
..106             throw({bad_request, "`keys` member must be a array."})
..107         end;
..108     _ ->
..109         ReqBody = couch_httpd:body(Req),
..110         {Props2} = ?JSON_DECODE(ReqBody),
..111         Keys = proplists:get_value(<<"keys">>, Props2, nil),
..112         couch_httpd_show:handle_view_list(Req#httpd{req_body=ReqBody},
..113             DName, ListName, ViewName, Db, Keys)
..114     end;
..115 
..116 handle_db_view_req(Req, _Db) ->
..117     send_method_not_allowed(Req, "GET,POST,HEAD").
..118 
..119 handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
..120     couch_stats_collector:increment({httpd, temporary_view_reads}),
..121     {Props} = couch_httpd:json_body_obj(Req),
..122     Language = proplists:get_value(<<"language">>, Props, <<"javascript">>),
..123     {DesignOptions} = proplists:get_value(<<"options">>, Props, {[]}),
..124     MapSrc = proplists:get_value(<<"map">>, Props),
..125     Keys = proplists:get_value(<<"keys">>, Props, nil),
..126     case proplists:get_value(<<"reduce">>, Props, null) of
..127     null ->
..128         QueryArgs = parse_view_params(Req, Keys, map),
..129         {ok, View, Group} = couch_view:get_temp_map_view(Db, Language,
..130             DesignOptions, MapSrc),
..131         output_map_view(Req, View, Group, Db, QueryArgs, Keys);
..132     RedSrc ->
..133         QueryArgs = parse_view_params(Req, Keys, reduce),
..134         {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language,
..135             DesignOptions, MapSrc, RedSrc),
..136         output_reduce_view(Req, View, Group, QueryArgs, Keys)
..137     end;
..138 
..139 handle_temp_view_req(Req, _Db) ->
..140     send_method_not_allowed(Req, "POST").
..141 
..142 output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
..143     #view_query_args{
..144         limit = Limit,
..145         direction = Dir,
..146         skip = SkipCount,
..147         start_key = StartKey,
..148         start_docid = StartDocId
..149     } = QueryArgs,
..150     CurrentEtag = view_group_etag(Group),
..151     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..152         {ok, RowCount} = couch_view:get_row_count(View),
..153         Start = {StartKey, StartDocId},
..154         FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
..155         FoldAccInit = {Limit, SkipCount, undefined, [], nil},
..156         FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
..157         finish_view_fold(Req, RowCount, FoldResult)
..158     end);
..159 
..160 output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
..161     #view_query_args{
..162         limit = Limit,
..163         direction = Dir,
..164         skip = SkipCount,
..165         start_docid = StartDocId
..166     } = QueryArgs,
..167     CurrentEtag = view_group_etag(Group, Keys),
..168     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..169         {ok, RowCount} = couch_view:get_row_count(View),
..170         FoldAccInit = {Limit, SkipCount, undefined, [], nil},
..171         FoldResult = lists:foldl(
..172             fun(Key, {ok, FoldAcc}) ->
..173                 Start = {Key, StartDocId},
..174                 FoldlFun = make_view_fold_fun(Req,
..175                     QueryArgs#view_query_args{
..176                         start_key = Key,
..177                         end_key = Key
..178                     }, CurrentEtag, Db, RowCount,
..179                     #view_fold_helper_funs{
..180                         reduce_count = fun couch_view:reduce_to_count/1
..181                     }),
..182                 couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
..183             end, {ok, FoldAccInit}, Keys),
..184         finish_view_fold(Req, RowCount, FoldResult)
..185     end).
..186 
..187 output_reduce_view(Req, View, Group, QueryArgs, nil) ->
..188     #view_query_args{
..189         start_key = StartKey,
..190         end_key = EndKey,
..191         limit = Limit,
..192         skip = Skip,
..193         direction = Dir,
..194         start_docid = StartDocId,
..195         end_docid = EndDocId,
..196         group_level = GroupLevel
..197     } = QueryArgs,
..198     CurrentEtag = view_group_etag(Group),
..199     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..200         {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
..201         FoldAccInit = {Limit, Skip, undefined, []},
..202         {ok, {_, _, Resp, _}} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
..203             {EndKey, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
..204         finish_reduce_fold(Req, Resp)
..205     end);
..206 
..207 output_reduce_view(Req, View, Group, QueryArgs, Keys) ->
..208     #view_query_args{
..209         limit = Limit,
..210         skip = Skip,
..211         direction = Dir,
..212         start_docid = StartDocId,
..213         end_docid = EndDocId,
..214         group_level = GroupLevel
..215     } = QueryArgs,
..216     CurrentEtag = view_group_etag(Group),
..217     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..218         {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
..219         {Resp, _RedAcc3} = lists:foldl(
..220             fun(Key, {Resp, RedAcc}) ->
..221                 % run the reduce once for each key in keys, with limit etc reapplied for each key
..222                 FoldAccInit = {Limit, Skip, Resp, RedAcc},
..223                 {_, {_, _, Resp2, RedAcc2}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},
..224                     {Key, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
..225                 % Switch to comma
..226                 {Resp2, RedAcc2}
..227             end,
..228         {undefined, []}, Keys), % Start with no comma
..229         finish_reduce_fold(Req, Resp)
..230     end).
..231 
..232 reverse_key_default(nil) -> {};
..233 reverse_key_default({}) -> nil;
..234 reverse_key_default(Key) -> Key.
..235 
..236 get_stale_type(Req) ->
..237     list_to_atom(couch_httpd:qs_value(Req, "stale", "nil")).
..238 
..239 get_reduce_type(Req) ->
..240     list_to_atom(couch_httpd:qs_value(Req, "reduce", "true")).
..241 
..242 parse_view_params(Req, Keys, ViewType) ->
..243     QueryList = couch_httpd:qs(Req),
..244     QueryParams =
..245     lists:foldl(fun({K, V}, Acc) ->
..246             parse_view_param(K, V) ++ Acc
..247         end, [], QueryList),
..248     IsMultiGet = case Keys of
..249         nil -> false;
..250         _ -> true
..251     end,
..252     Args = #view_query_args{
..253         view_type=ViewType,
..254         multi_get=IsMultiGet
..255     },
..256     QueryArgs = lists:foldl(fun({K, V}, Args2) ->
..257         validate_view_query(K, V, Args2)
..258     end, Args, lists:reverse(QueryParams)), % Reverse to match QS order.
..259 
..260     GroupLevel = QueryArgs#view_query_args.group_level,
..261     case {ViewType, GroupLevel, IsMultiGet} of
..262         {reduce, exact, true} ->
..263             QueryArgs;
..264         {reduce, _, false} ->
..265             QueryArgs;
..266         {reduce, _, _} ->
..267             Msg = <<"Multi-key fetchs for reduce "
..268                     "view must include `group=true`">>,
..269             throw({query_parse_error, Msg});
..270         _ ->
..271             QueryArgs
..272     end,
..273     QueryArgs.
..274 
..275 parse_view_param("", _) ->
..276     [];
..277 parse_view_param("key", Value) ->
..278     JsonKey = ?JSON_DECODE(Value),
..279     [{start_key, JsonKey}, {end_key, JsonKey}];
..280 parse_view_param("startkey_docid", Value) ->
..281     [{start_docid, ?l2b(Value)}];
..282 parse_view_param("endkey_docid", Value) ->
..283     [{end_docid, ?l2b(Value)}];
..284 parse_view_param("startkey", Value) ->
..285     [{start_key, ?JSON_DECODE(Value)}];
..286 parse_view_param("endkey", Value) ->
..287     [{end_key, ?JSON_DECODE(Value)}];
..288 parse_view_param("limit", Value) ->
..289     [{limit, parse_positive_int_param(Value)}];
..290 parse_view_param("count", _Value) ->
..291     throw({query_parse_error, <<"Query parameter 'count' is now 'limit'.">>});
..292 parse_view_param("stale", "ok") ->
..293     [{stale, ok}];
..294 parse_view_param("stale", _Value) ->
..295     throw({query_parse_error, <<"stale only available as stale=ok">>});
..296 parse_view_param("update", _Value) ->
..297     throw({query_parse_error, <<"update=false is now stale=ok">>});
..298 parse_view_param("descending", Value) ->
..299     [{descending, parse_bool_param(Value)}];
..300 parse_view_param("skip", Value) ->
..301     [{skip, parse_int_param(Value)}];
..302 parse_view_param("group", Value) ->
..303     case parse_bool_param(Value) of
..304         true -> [{group_level, exact}];
..305         false -> [{group_level, 0}]
..306     end;
..307 parse_view_param("group_level", Value) ->
..308     [{group_level, parse_positive_int_param(Value)}];
..309 parse_view_param("inclusive_end", Value) ->
..310     [{inclusive_end, parse_bool_param(Value)}];
..311 parse_view_param("reduce", Value) ->
..312     [{reduce, parse_bool_param(Value)}];
..313 parse_view_param("include_docs", Value) ->
..314     [{include_docs, parse_bool_param(Value)}];
..315 parse_view_param("list", Value) ->
..316     [{list, ?l2b(Value)}];
..317 parse_view_param("callback", _) ->
..318     []; % Verified in the JSON response functions
..319 parse_view_param(Key, Value) ->
..320     [{extra, {Key, Value}}].
..321 
..322 validate_view_query(start_key, Value, Args) ->
..323     case Args#view_query_args.multi_get of
..324         true ->
..325             Msg = <<"Query parameter `start_key` is "
..326                     "not compatiible with multi-get">>,
..327             throw({query_parse_error, Msg});
..328         _ ->
..329             Args#view_query_args{start_key=Value}
..330     end;
..331 validate_view_query(start_docid, Value, Args) ->
..332     Args#view_query_args{start_docid=Value};
..333 validate_view_query(end_key, Value, Args) ->
..334     case Args#view_query_args.multi_get of
..335         true->
..336             Msg = <<"Query paramter `end_key` is "
..337                     "not compatibile with multi-get">>,
..338             throw({query_parse_error, Msg});
..339         _ ->
..340             Args#view_query_args{end_key=Value}
..341     end;
..342 validate_view_query(end_docid, Value, Args) ->
..343     Args#view_query_args{end_docid=Value};
..344 validate_view_query(limit, Value, Args) ->
..345     Args#view_query_args{limit=Value};
..346 validate_view_query(list, Value, Args) ->
..347     Args#view_query_args{list=Value};
..348 validate_view_query(stale, _, Args) ->
..349     Args;
..350 validate_view_query(descending, true, Args) ->
..351     case Args#view_query_args.direction of
..352         rev -> Args; % Already reversed
..353         fwd ->
..354             Args#view_query_args{
..355                 direction = rev,
..356                 start_key =
..357                     reverse_key_default(Args#view_query_args.start_key),
..358                 start_docid =
..359                     reverse_key_default(Args#view_query_args.start_docid),
..360                 end_key =
..361                     reverse_key_default(Args#view_query_args.end_key),
..362                 end_docid =
..363                     reverse_key_default(Args#view_query_args.end_docid)
..364             }
..365     end;
..366 validate_view_query(descending, false, Args) ->
..367     Args; % Ignore default condition
..368 validate_view_query(skip, Value, Args) ->
..369     Args#view_query_args{skip=Value};
..370 validate_view_query(group_level, Value, Args) ->
..371     case Args#view_query_args.view_type of
..372         reduce ->
..373             Args#view_query_args{group_level=Value};
..374         _ ->
..375             Msg = <<"Invalid URL parameter 'group' or "
..376                     " 'group_level' for non-reduce view.">>,
..377             throw({query_parse_error, Msg})
..378     end;
..379 validate_view_query(inclusive_end, Value, Args) ->
..380     Args#view_query_args{inclusive_end=Value};
..381 validate_view_query(reduce, _, Args) ->
..382     case Args#view_query_args.view_type of
..383         map ->
..384             Msg = <<"Invalid URL parameter `reduce` for map view.">>,
..385             throw({query_parse_error, Msg});
..386         _ ->
..387             Args
..388     end;
..389 validate_view_query(include_docs, true, Args) ->
..390     case Args#view_query_args.view_type of
..391         reduce ->
..392             Msg = <<"Query paramter `include_docs` "
..393                     "is invalid for reduce views.">>,
..394             throw({query_parse_error, Msg});
..395         _ ->
..396             Args#view_query_args{include_docs=true}
..397     end;
..398 validate_view_query(include_docs, _Value, Args) ->
..399     Args;
..400 validate_view_query(extra, _Value, Args) ->
..401     Args.
..402 
..403 make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
..404     #view_query_args{
..405         end_key = EndKey,
..406         end_docid = EndDocId,
..407         inclusive_end = InclusiveEnd,
..408         direction = Dir
..409     } = QueryArgs,
..410 
..411     #view_fold_helper_funs{
..412         passed_end = PassedEndFun,
..413         start_response = StartRespFun,
..414         send_row = SendRowFun,
..415         reduce_count = ReduceCountFun
..416     } = apply_default_helper_funs(HelperFuns,
..417         {Dir, EndKey, EndDocId, InclusiveEnd}),
..418 
..419     #view_query_args{
..420         include_docs = IncludeDocs
..421     } = QueryArgs,
..422 
..423     fun({{Key, DocId}, Value}, OffsetReds, {AccLimit, AccSkip, Resp, RowFunAcc,
..424                                             OffsetAcc}) ->
..425         PassedEnd = PassedEndFun(Key, DocId),
..426         case {PassedEnd, AccLimit, AccSkip, Resp} of
..427         {true, _, _, _} ->
..428             % The stop key has been passed, stop looping.
..429             % We may need offset so calcluate it here.
..430             % Checking Resp is an optimization that tells
..431             % us its already been calculated (and sent).
..432             NewOffset = case Resp of
..433                 undefined -> ReduceCountFun(OffsetReds);
..434                 _ -> nil
..435             end,
..436             {stop, {AccLimit, AccSkip, Resp, RowFunAcc, NewOffset}};
..437         {_, 0, _, _} ->
..438             % we've done "limit" rows, stop foldling
..439             {stop, {0, 0, Resp, RowFunAcc, OffsetAcc}};
..440         {_, _, AccSkip, _} when AccSkip > 0 ->
..441             % just keep skipping
..442             {ok, {AccLimit, AccSkip - 1, Resp, RowFunAcc, OffsetAcc}};
..443         {_, _, _, undefined} ->
..444             % rendering the first row, first we start the response
..445             Offset = ReduceCountFun(OffsetReds),
..446             {ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag,
..447                 TotalViewCount, Offset, RowFunAcc),
..448             {Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value},
..449                 IncludeDocs, RowFunAcc0),
..450             {Go, {AccLimit - 1, 0, Resp2, RowFunAcc2, Offset}};
..451         {_, AccLimit, _, Resp} when (AccLimit > 0) ->
..452             % rendering all other rows
..453             {Go, RowFunAcc2} = SendRowFun(Resp, Db, {{Key, DocId}, Value},
..454                 IncludeDocs, RowFunAcc),
..455             {Go, {AccLimit - 1, 0, Resp, RowFunAcc2, OffsetAcc}}
..456         end
..457     end.
..458 
..459 make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
..460     #reduce_fold_helper_funs{
..461         start_response = StartRespFun,
..462         send_row = SendRowFun
..463     } = apply_default_helper_funs(HelperFuns),
..464 
..465     GroupRowsFun =
..466         fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 ->
..467             true;
..468         ({Key1,_}, {Key2,_})
..469                 when is_integer(GroupLevel) and is_list(Key1) and is_list(Key2) ->
..470             lists:sublist(Key1, GroupLevel) == lists:sublist(Key2, GroupLevel);
..471         ({Key1,_}, {Key2,_}) ->
..472             Key1 == Key2
..473         end,
..474 
..475     RespFun = fun
..476     (_Key, _Red, {AccLimit, AccSkip, Resp, RowAcc}) when AccSkip > 0 ->
..477         % keep skipping
..478         {ok, {AccLimit, AccSkip - 1, Resp, RowAcc}};
..479     (_Key, _Red, {0, _AccSkip, Resp, RowAcc}) ->
..480         % we've exhausted limit rows, stop
..481         {stop, {0, _AccSkip, Resp, RowAcc}};
..482 
..483     (_Key, Red, {AccLimit, 0, undefined, RowAcc0}) when GroupLevel == 0 ->
..484         % we haven't started responding yet and group=false
..485         {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
..486         {Go, RowAcc2} = SendRowFun(Resp2, {null, Red}, RowAcc),
..487         {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
..488     (_Key, Red, {AccLimit, 0, Resp, RowAcc}) when GroupLevel == 0 ->
..489         % group=false but we've already started the response
..490         {Go, RowAcc2} = SendRowFun(Resp, {null, Red}, RowAcc),
..491         {Go, {AccLimit - 1, 0, Resp, RowAcc2}};
..492 
..493     (Key, Red, {AccLimit, 0, undefined, RowAcc0})
..494             when is_integer(GroupLevel), is_list(Key) ->
..495         % group_level and we haven't responded yet
..496         {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
..497         {Go, RowAcc2} = SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowAcc),
..498         {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
..499     (Key, Red, {AccLimit, 0, Resp, RowAcc})
..500             when is_integer(GroupLevel), is_list(Key) ->
..501         % group_level and we've already started the response
..502         {Go, RowAcc2} = SendRowFun(Resp, {lists:sublist(Key, GroupLevel), Red}, RowAcc),
..503         {Go, {AccLimit - 1, 0, Resp, RowAcc2}};
..504 
..505     (Key, Red, {AccLimit, 0, undefined, RowAcc0}) ->
..506         % group=true and we haven't responded yet
..507         {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
..508         {Go, RowAcc2} = SendRowFun(Resp2, {Key, Red}, RowAcc),
..509         {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
..510     (Key, Red, {AccLimit, 0, Resp, RowAcc}) ->
..511         % group=true and we've already started the response
..512         {Go, RowAcc2} = SendRowFun(Resp, {Key, Red}, RowAcc),
..513         {Go, {AccLimit - 1, 0, Resp, RowAcc2}}
..514     end,
..515     {ok, GroupRowsFun, RespFun}.
..516 
..517 apply_default_helper_funs(#view_fold_helper_funs{
..518     passed_end = PassedEnd,
..519     start_response = StartResp,
..520     send_row = SendRow
..521 }=Helpers, {Dir, EndKey, EndDocId, InclusiveEnd}) ->
..522     PassedEnd2 = case PassedEnd of
..523     undefined -> make_passed_end_fun(Dir, EndKey, EndDocId, InclusiveEnd);
..524     _ -> PassedEnd
..525     end,
..526 
..527     StartResp2 = case StartResp of
..528     undefined -> fun json_view_start_resp/5;
..529     _ -> StartResp
..530     end,
..531 
..532     SendRow2 = case SendRow of
..533     undefined -> fun send_json_view_row/5;
..534     _ -> SendRow
..535     end,
..536 
..537     Helpers#view_fold_helper_funs{
..538         passed_end = PassedEnd2,
..539         start_response = StartResp2,
..540         send_row = SendRow2
..541     }.
..542 
..543 apply_default_helper_funs(#reduce_fold_helper_funs{
..544     start_response = StartResp,
..545     send_row = SendRow
..546 }=Helpers) ->
..547     StartResp2 = case StartResp of
..548     undefined -> fun json_reduce_start_resp/3;
..549     _ -> StartResp
..550     end,
..551 
..552     SendRow2 = case SendRow of
..553     undefined -> fun send_json_reduce_row/3;
..554     _ -> SendRow
..555     end,
..556 
..557     Helpers#reduce_fold_helper_funs{
..558         start_response = StartResp2,
..559         send_row = SendRow2
..560     }.
..561 
..562 make_passed_end_fun(fwd, EndKey, EndDocId, InclusiveEnd) ->
..563     case InclusiveEnd of
..564     true ->
..565         fun(ViewKey, ViewId) ->
..566             couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
..567         end;
..568     false ->
..569         fun
..570             (ViewKey, _ViewId) when ViewKey == EndKey ->
..571                 true;
..572             (ViewKey, ViewId) ->
..573                 couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
..574         end
..575     end;
..576 
..577 make_passed_end_fun(rev, EndKey, EndDocId, InclusiveEnd) ->
..578     case InclusiveEnd of
..579     true ->
..580         fun(ViewKey, ViewId) ->
..581             couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
..582         end;
..583     false->
..584         fun
..585             (ViewKey, _ViewId) when ViewKey == EndKey ->
..586                 true;
..587             (ViewKey, ViewId) ->
..588                 couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
..589         end
..590     end.
..591 
..592 json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc) ->
..593     {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
..594     BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
..595             [TotalViewCount, Offset]),
..596     {ok, Resp, BeginBody}.
..597 
..598 send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) ->
..599     JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
..600     send_chunk(Resp, RowFront ++  ?JSON_ENCODE(JsonObj)),
..601     {ok, ",\r\n"}.
..602 
..603 json_reduce_start_resp(Req, Etag, _Acc0) ->
..604     {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
..605     {ok, Resp, "{\"rows\":[\r\n"}.
..606 
..607 send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
..608     send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})),
..609     {ok, ",\r\n"}.
..610 
..611 view_group_etag(Group) ->
..612     view_group_etag(Group, nil).
..613 
..614 view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, Extra) ->
..615     % This is not as granular as it could be.
..616     % If there are updates to the db that do not effect the view index,
..617     % they will change the Etag. For more granular Etags we'd need to keep
..618     % track of the last Db seq that caused an index change.
..619     couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
..620 
..621 % the view row has an error
..622 view_row_obj(_Db, {{Key, error}, Value}, _IncludeDocs) ->
..623     {[{key, Key}, {error, Value}]};
..624 % include docs in the view output
..625 view_row_obj(Db, {{Key, DocId}, {Props}}, true) ->
..626     Rev = case proplists:get_value(<<"_rev">>, Props) of
..627     undefined ->
..628         nil;
..629     Rev0 ->
..630         couch_doc:parse_rev(Rev0)
..631     end,
..632     view_row_with_doc(Db, {{Key, DocId}, {Props}}, Rev);
..633 view_row_obj(Db, {{Key, DocId}, Value}, true) ->
..634     view_row_with_doc(Db, {{Key, DocId}, Value}, nil);
..635 % the normal case for rendering a view row
..636 view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs) ->
..637     {[{id, DocId}, {key, Key}, {value, Value}]}.
..638 
..639 view_row_with_doc(Db, {{Key, DocId}, Value}, Rev) ->
..640     ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]),
..641     case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of
..642       {{not_found, missing}, _RevId} ->
..643           {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
..644       {not_found, missing} ->
..645           {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
..646       {not_found, deleted} ->
..647           {[{id, DocId}, {key, Key}, {value, Value}]};
..648       Doc ->
..649         JsonDoc = couch_doc:to_json_obj(Doc, []),
..650         {[{id, DocId}, {key, Key}, {value, Value}, {doc, JsonDoc}]}
..651     end.
..652 
..653 finish_view_fold(Req, TotalRows, FoldResult) ->
..654     case FoldResult of
..655     {ok, {_, _, undefined, _, Offset}} ->
..656         % nothing found in the view, nothing has been returned
..657         % send empty view
..658         NewOffset = case Offset of
..659             nil -> TotalRows;
..660             _ -> Offset
..661         end,
..662         send_json(Req, 200, {[
..663             {total_rows, TotalRows},
..664             {offset, NewOffset},
..665             {rows, []}
..666         ]});
..667     {ok, {_, _, Resp, _, _}} ->
..668         % end the view
..669         send_chunk(Resp, "\r\n]}"),
..670         end_json_response(Resp);
..671     Error ->
..672         throw(Error)
..673     end.
..674 
..675 finish_reduce_fold(Req, Resp) ->
..676     case Resp of
..677     undefined ->
..678         send_json(Req, 200, {[
..679             {rows, []}
..680         ]});
..681     Resp ->
..682         send_chunk(Resp, "\r\n]}"),
..683         end_json_response(Resp)
..684     end.
..685 
..686 parse_bool_param("true") -> true;
..687 parse_bool_param("false") -> false;
..688 parse_bool_param(Val) ->
..689     Msg = io_lib:format("Invalid value for boolean paramter: ~p", [Val]),
..690     throw({query_parse_error, ?l2b(Msg)}).
..691 
..692 parse_int_param(Val) ->
..693     case (catch list_to_integer(Val)) of
..694     IntVal when is_integer(IntVal) ->
..695         IntVal;
..696     _ ->
..697         Msg = io_lib:format("Invalid value for integer parameter: ~p", [Val]),
..698         throw({query_parse_error, ?l2b(Msg)})
..699     end.
..700 
..701 parse_positive_int_param(Val) ->
..702     case parse_int_param(Val) of
..703     IntVal when IntVal >= 0 ->
..704         IntVal;
..705     _ ->
..706         Fmt = "Invalid value for positive integer parameter: ~p",
..707         Msg = io_lib:format(Fmt, [Val]),
..708         throw({query_parse_error, ?l2b(Msg)})
..709     end.
..710 

Generated using etap 0.3.4.