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 | ?? | ?? | ?? |
|
....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.