From 81bdbed444df2cbcf3cdb32f7d4a74019de06454 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Wed, 11 Aug 2010 15:22:33 -0400 Subject: reorganize couch .erl and driver code into rebar layout --- src/couchdb/couch_httpd_view.erl | 692 --------------------------------------- 1 file changed, 692 deletions(-) delete mode 100644 src/couchdb/couch_httpd_view.erl (limited to 'src/couchdb/couch_httpd_view.erl') diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl deleted file mode 100644 index e1a0dfad..00000000 --- a/src/couchdb/couch_httpd_view.erl +++ /dev/null @@ -1,692 +0,0 @@ -% Licensed under the Apache License, Version 2.0 (the "License"); you may not -% use this file except in compliance with the License. You may obtain a copy of -% the License at -% -% http://www.apache.org/licenses/LICENSE-2.0 -% -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -% License for the specific language governing permissions and limitations under -% the License. - --module(couch_httpd_view). --include("couch_db.hrl"). - --export([handle_view_req/3,handle_temp_view_req/2]). - --export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]). --export([make_view_fold_fun/7, finish_view_fold/4, finish_view_fold/5, view_row_obj/3]). --export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/6]). --export([design_doc_view/5, parse_bool_param/1, doc_member/2]). --export([make_key_options/1, load_view/4]). - --import(couch_httpd, - [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2, - start_json_response/2, start_json_response/3, end_json_response/1, - send_chunked_error/2]). - --import(couch_db,[get_update_seq/1]). - -design_doc_view(Req, Db, DName, ViewName, Keys) -> - DesignId = <<"_design/", DName/binary>>, - Stale = get_stale_type(Req), - Reduce = get_reduce_type(Req), - Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of - {ok, View, Group} -> - QueryArgs = parse_view_params(Req, Keys, map), - output_map_view(Req, View, Group, Db, QueryArgs, Keys); - {not_found, Reason} -> - case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of - {ok, ReduceView, Group} -> - case Reduce of - false -> - QueryArgs = parse_view_params(Req, Keys, red_map), - MapView = couch_view:extract_map_view(ReduceView), - output_map_view(Req, MapView, Group, Db, QueryArgs, Keys); - _ -> - QueryArgs = parse_view_params(Req, Keys, reduce), - output_reduce_view(Req, Db, ReduceView, Group, QueryArgs, Keys) - end; - _ -> - throw({not_found, Reason}) - end - end, - couch_stats_collector:increment({httpd, view_reads}), - Result. - -handle_view_req(#httpd{method='GET', - path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) -> - design_doc_view(Req, Db, DName, ViewName, nil); - -handle_view_req(#httpd{method='POST', - path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) -> - couch_httpd:validate_ctype(Req, "application/json"), - {Fields} = couch_httpd:json_body_obj(Req), - case couch_util:get_value(<<"keys">>, Fields, nil) of - nil -> - Fmt = "POST to view ~p/~p in database ~p with no keys member.", - ?LOG_DEBUG(Fmt, [DName, ViewName, Db]), - design_doc_view(Req, Db, DName, ViewName, nil); - Keys when is_list(Keys) -> - design_doc_view(Req, Db, DName, ViewName, Keys); - _ -> - throw({bad_request, "`keys` member must be a array."}) - end; - -handle_view_req(Req, _Db, _DDoc) -> - send_method_not_allowed(Req, "GET,POST,HEAD"). - -handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> - couch_httpd:validate_ctype(Req, "application/json"), - ok = couch_db:check_is_admin(Db), - couch_stats_collector:increment({httpd, temporary_view_reads}), - {Props} = couch_httpd:json_body_obj(Req), - Language = couch_util:get_value(<<"language">>, Props, <<"javascript">>), - {DesignOptions} = couch_util:get_value(<<"options">>, Props, {[]}), - MapSrc = couch_util:get_value(<<"map">>, Props), - Keys = couch_util:get_value(<<"keys">>, Props, nil), - Reduce = get_reduce_type(Req), - case couch_util:get_value(<<"reduce">>, Props, null) of - null -> - QueryArgs = parse_view_params(Req, Keys, map), - {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, - DesignOptions, MapSrc), - output_map_view(Req, View, Group, Db, QueryArgs, Keys); - _ when Reduce =:= false -> - QueryArgs = parse_view_params(Req, Keys, red_map), - {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, - DesignOptions, MapSrc), - output_map_view(Req, View, Group, Db, QueryArgs, Keys); - RedSrc -> - QueryArgs = parse_view_params(Req, Keys, reduce), - {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language, - DesignOptions, MapSrc, RedSrc), - output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) - end; - -handle_temp_view_req(Req, _Db) -> - send_method_not_allowed(Req, "POST"). - -output_map_view(Req, View, Group, Db, QueryArgs, nil) -> - #view_query_args{ - limit = Limit, - skip = SkipCount - } = QueryArgs, - CurrentEtag = view_group_etag(Group, Db), - couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, RowCount} = couch_view:get_row_count(View), - FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, Group#group.current_seq, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}), - FoldAccInit = {Limit, SkipCount, undefined, []}, - {ok, LastReduce, FoldResult} = couch_view:fold(View, - FoldlFun, FoldAccInit, make_key_options(QueryArgs)), - finish_view_fold(Req, RowCount, - couch_view:reduce_to_count(LastReduce), FoldResult) - end); - -output_map_view(Req, View, Group, Db, QueryArgs, Keys) -> - #view_query_args{ - limit = Limit, - skip = SkipCount - } = QueryArgs, - CurrentEtag = view_group_etag(Group, Db, Keys), - couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, RowCount} = couch_view:get_row_count(View), - FoldAccInit = {Limit, SkipCount, undefined, []}, - {LastReduce, FoldResult} = lists:foldl(fun(Key, {_, FoldAcc}) -> - FoldlFun = make_view_fold_fun(Req, QueryArgs#view_query_args{}, - CurrentEtag, Db, Group#group.current_seq, RowCount, - #view_fold_helper_funs{ - reduce_count = fun couch_view:reduce_to_count/1 - }), - {ok, LastReduce, FoldResult} = couch_view:fold(View, FoldlFun, - FoldAcc, make_key_options( - QueryArgs#view_query_args{start_key=Key, end_key=Key})), - {LastReduce, FoldResult} - end, {{[],[]}, FoldAccInit}, Keys), - finish_view_fold(Req, RowCount, couch_view:reduce_to_count(LastReduce), - FoldResult, [{update_seq,Group#group.current_seq}]) - end). - -output_reduce_view(Req, Db, View, Group, QueryArgs, nil) -> - #view_query_args{ - limit = Limit, - skip = Skip, - group_level = GroupLevel - } = QueryArgs, - CurrentEtag = view_group_etag(Group, Db), - couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, - QueryArgs, CurrentEtag, Group#group.current_seq, - #reduce_fold_helper_funs{}), - FoldAccInit = {Limit, Skip, undefined, []}, - {ok, {_, _, Resp, _}} = couch_view:fold_reduce(View, - RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} | - make_key_options(QueryArgs)]), - finish_reduce_fold(Req, Resp) - end); - -output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) -> - #view_query_args{ - limit = Limit, - skip = Skip, - group_level = GroupLevel - } = QueryArgs, - CurrentEtag = view_group_etag(Group, Db, Keys), - couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, - QueryArgs, CurrentEtag, Group#group.current_seq, - #reduce_fold_helper_funs{}), - {Resp, _RedAcc3} = lists:foldl( - fun(Key, {Resp, RedAcc}) -> - % run the reduce once for each key in keys, with limit etc - % reapplied for each key - FoldAccInit = {Limit, Skip, Resp, RedAcc}, - {_, {_, _, Resp2, RedAcc2}} = couch_view:fold_reduce(View, - RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} | - make_key_options(QueryArgs#view_query_args{ - start_key=Key, end_key=Key})]), - % Switch to comma - {Resp2, RedAcc2} - end, - {undefined, []}, Keys), % Start with no comma - finish_reduce_fold(Req, Resp, [{update_seq,Group#group.current_seq}]) - end). - -reverse_key_default(?MIN_STR) -> ?MAX_STR; -reverse_key_default(?MAX_STR) -> ?MIN_STR; -reverse_key_default(Key) -> Key. - -get_stale_type(Req) -> - list_to_existing_atom(couch_httpd:qs_value(Req, "stale", "nil")). - -get_reduce_type(Req) -> - list_to_existing_atom(couch_httpd:qs_value(Req, "reduce", "true")). - -load_view(Req, Db, {ViewDesignId, ViewName}, Keys) -> - Stale = get_stale_type(Req), - Reduce = get_reduce_type(Req), - case couch_view:get_map_view(Db, ViewDesignId, ViewName, Stale) of - {ok, View, Group} -> - QueryArgs = parse_view_params(Req, Keys, map), - {map, View, Group, QueryArgs}; - {not_found, _Reason} -> - case couch_view:get_reduce_view(Db, ViewDesignId, ViewName, Stale) of - {ok, ReduceView, Group} -> - case Reduce of - false -> - QueryArgs = parse_view_params(Req, Keys, map_red), - MapView = couch_view:extract_map_view(ReduceView), - {map, MapView, Group, QueryArgs}; - _ -> - QueryArgs = parse_view_params(Req, Keys, reduce), - {reduce, ReduceView, Group, QueryArgs} - end; - {not_found, Reason} -> - throw({not_found, Reason}) - end - end. - -% query_parse_error could be removed -% we wouldn't need to pass the view type, it'd just parse params. -% I'm not sure what to do about the error handling, but -% it might simplify things to have a parse_view_params function -% that doesn't throw(). -parse_view_params(Req, Keys, ViewType) -> - QueryList = couch_httpd:qs(Req), - QueryParams = - lists:foldl(fun({K, V}, Acc) -> - parse_view_param(K, V) ++ Acc - end, [], QueryList), - IsMultiGet = (Keys =/= nil), - Args = #view_query_args{ - view_type=ViewType, - multi_get=IsMultiGet - }, - QueryArgs = lists:foldl(fun({K, V}, Args2) -> - validate_view_query(K, V, Args2) - end, Args, lists:reverse(QueryParams)), % Reverse to match QS order. - - GroupLevel = QueryArgs#view_query_args.group_level, - case {ViewType, GroupLevel, IsMultiGet} of - {reduce, exact, true} -> - QueryArgs; - {reduce, _, false} -> - QueryArgs; - {reduce, _, _} -> - % we can simplify code if we just drop this error message. - Msg = <<"Multi-key fetchs for reduce " - "view must include `group=true`">>, - throw({query_parse_error, Msg}); - _ -> - QueryArgs - end, - QueryArgs. - -parse_view_param("", _) -> - []; -parse_view_param("key", Value) -> - JsonKey = ?JSON_DECODE(Value), - [{start_key, JsonKey}, {end_key, JsonKey}]; -parse_view_param("startkey_docid", Value) -> - [{start_docid, ?l2b(Value)}]; -parse_view_param("endkey_docid", Value) -> - [{end_docid, ?l2b(Value)}]; -parse_view_param("startkey", Value) -> - [{start_key, ?JSON_DECODE(Value)}]; -parse_view_param("endkey", Value) -> - [{end_key, ?JSON_DECODE(Value)}]; -parse_view_param("limit", Value) -> - [{limit, parse_positive_int_param(Value)}]; -parse_view_param("count", _Value) -> - throw({query_parse_error, <<"Query parameter 'count' is now 'limit'.">>}); -parse_view_param("stale", "ok") -> - [{stale, ok}]; -parse_view_param("stale", _Value) -> - throw({query_parse_error, <<"stale only available as stale=ok">>}); -parse_view_param("update", _Value) -> - throw({query_parse_error, <<"update=false is now stale=ok">>}); -parse_view_param("descending", Value) -> - [{descending, parse_bool_param(Value)}]; -parse_view_param("skip", Value) -> - [{skip, parse_int_param(Value)}]; -parse_view_param("group", Value) -> - case parse_bool_param(Value) of - true -> [{group_level, exact}]; - false -> [{group_level, 0}] - end; -parse_view_param("group_level", Value) -> - [{group_level, parse_positive_int_param(Value)}]; -parse_view_param("inclusive_end", Value) -> - [{inclusive_end, parse_bool_param(Value)}]; -parse_view_param("reduce", Value) -> - [{reduce, parse_bool_param(Value)}]; -parse_view_param("include_docs", Value) -> - [{include_docs, parse_bool_param(Value)}]; -parse_view_param("list", Value) -> - [{list, ?l2b(Value)}]; -parse_view_param("callback", _) -> - []; % Verified in the JSON response functions -parse_view_param(Key, Value) -> - [{extra, {Key, Value}}]. - -validate_view_query(start_key, Value, Args) -> - case Args#view_query_args.multi_get of - true -> - Msg = <<"Query parameter `start_key` is " - "not compatible with multi-get">>, - throw({query_parse_error, Msg}); - _ -> - Args#view_query_args{start_key=Value} - end; -validate_view_query(start_docid, Value, Args) -> - Args#view_query_args{start_docid=Value}; -validate_view_query(end_key, Value, Args) -> - case Args#view_query_args.multi_get of - true-> - Msg = <<"Query parameter `end_key` is " - "not compatible with multi-get">>, - throw({query_parse_error, Msg}); - _ -> - Args#view_query_args{end_key=Value} - end; -validate_view_query(end_docid, Value, Args) -> - Args#view_query_args{end_docid=Value}; -validate_view_query(limit, Value, Args) -> - Args#view_query_args{limit=Value}; -validate_view_query(list, Value, Args) -> - Args#view_query_args{list=Value}; -validate_view_query(stale, _, Args) -> - Args; -validate_view_query(descending, true, Args) -> - case Args#view_query_args.direction of - rev -> Args; % Already reversed - fwd -> - Args#view_query_args{ - direction = rev, - start_docid = - reverse_key_default(Args#view_query_args.start_docid), - end_docid = - reverse_key_default(Args#view_query_args.end_docid) - } - end; -validate_view_query(descending, false, Args) -> - Args; % Ignore default condition -validate_view_query(skip, Value, Args) -> - Args#view_query_args{skip=Value}; -validate_view_query(group_level, Value, Args) -> - case Args#view_query_args.view_type of - reduce -> - Args#view_query_args{group_level=Value}; - _ -> - Msg = <<"Invalid URL parameter 'group' or " - " 'group_level' for non-reduce view.">>, - throw({query_parse_error, Msg}) - end; -validate_view_query(inclusive_end, Value, Args) -> - Args#view_query_args{inclusive_end=Value}; -validate_view_query(reduce, _, Args) -> - case Args#view_query_args.view_type of - map -> - Msg = <<"Invalid URL parameter `reduce` for map view.">>, - throw({query_parse_error, Msg}); - _ -> - Args - end; -validate_view_query(include_docs, true, Args) -> - case Args#view_query_args.view_type of - reduce -> - Msg = <<"Query parameter `include_docs` " - "is invalid for reduce views.">>, - throw({query_parse_error, Msg}); - _ -> - Args#view_query_args{include_docs=true} - end; -% Use the view_query_args record's default value -validate_view_query(include_docs, _Value, Args) -> - Args; -validate_view_query(extra, _Value, Args) -> - Args. - -make_view_fold_fun(Req, QueryArgs, Etag, Db, UpdateSeq, TotalViewCount, HelperFuns) -> - #view_fold_helper_funs{ - start_response = StartRespFun, - send_row = SendRowFun, - reduce_count = ReduceCountFun - } = apply_default_helper_funs(HelperFuns), - - #view_query_args{ - include_docs = IncludeDocs - } = QueryArgs, - - fun({{Key, DocId}, Value}, OffsetReds, - {AccLimit, AccSkip, Resp, RowFunAcc}) -> - case {AccLimit, AccSkip, Resp} of - {0, _, _} -> - % we've done "limit" rows, stop foldling - {stop, {0, 0, Resp, RowFunAcc}}; - {_, AccSkip, _} when AccSkip > 0 -> - % just keep skipping - {ok, {AccLimit, AccSkip - 1, Resp, RowFunAcc}}; - {_, _, undefined} -> - % rendering the first row, first we start the response - Offset = ReduceCountFun(OffsetReds), - {ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag, - TotalViewCount, Offset, RowFunAcc, UpdateSeq), - {Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value}, - IncludeDocs, RowFunAcc0), - {Go, {AccLimit - 1, 0, Resp2, RowFunAcc2}}; - {AccLimit, _, Resp} when (AccLimit > 0) -> - % rendering all other rows - {Go, RowFunAcc2} = SendRowFun(Resp, Db, {{Key, DocId}, Value}, - IncludeDocs, RowFunAcc), - {Go, {AccLimit - 1, 0, Resp, RowFunAcc2}} - end - end. - -make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, UpdateSeq, HelperFuns) -> - #reduce_fold_helper_funs{ - start_response = StartRespFun, - send_row = SendRowFun - } = apply_default_helper_funs(HelperFuns), - - GroupRowsFun = - fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 -> - true; - ({Key1,_}, {Key2,_}) - when is_integer(GroupLevel) and is_list(Key1) and is_list(Key2) -> - lists:sublist(Key1, GroupLevel) == lists:sublist(Key2, GroupLevel); - ({Key1,_}, {Key2,_}) -> - Key1 == Key2 - end, - - RespFun = fun - (_Key, _Red, {AccLimit, AccSkip, Resp, RowAcc}) when AccSkip > 0 -> - % keep skipping - {ok, {AccLimit, AccSkip - 1, Resp, RowAcc}}; - (_Key, _Red, {0, _AccSkip, Resp, RowAcc}) -> - % we've exhausted limit rows, stop - {stop, {0, _AccSkip, Resp, RowAcc}}; - - (_Key, Red, {AccLimit, 0, undefined, RowAcc0}) when GroupLevel == 0 -> - % we haven't started responding yet and group=false - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0, UpdateSeq), - {Go, RowAcc2} = SendRowFun(Resp2, {null, Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; - (_Key, Red, {AccLimit, 0, Resp, RowAcc}) when GroupLevel == 0 -> - % group=false but we've already started the response - {Go, RowAcc2} = SendRowFun(Resp, {null, Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp, RowAcc2}}; - - (Key, Red, {AccLimit, 0, undefined, RowAcc0}) - when is_integer(GroupLevel), is_list(Key) -> - % group_level and we haven't responded yet - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0, UpdateSeq), - {Go, RowAcc2} = SendRowFun(Resp2, - {lists:sublist(Key, GroupLevel), Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; - (Key, Red, {AccLimit, 0, Resp, RowAcc}) - when is_integer(GroupLevel), is_list(Key) -> - % group_level and we've already started the response - {Go, RowAcc2} = SendRowFun(Resp, - {lists:sublist(Key, GroupLevel), Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp, RowAcc2}}; - - (Key, Red, {AccLimit, 0, undefined, RowAcc0}) -> - % group=true and we haven't responded yet - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0, UpdateSeq), - {Go, RowAcc2} = SendRowFun(Resp2, {Key, Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; - (Key, Red, {AccLimit, 0, Resp, RowAcc}) -> - % group=true and we've already started the response - {Go, RowAcc2} = SendRowFun(Resp, {Key, Red}, RowAcc), - {Go, {AccLimit - 1, 0, Resp, RowAcc2}} - end, - {ok, GroupRowsFun, RespFun}. - -apply_default_helper_funs( - #view_fold_helper_funs{ - start_response = StartResp, - send_row = SendRow - }=Helpers) -> - StartResp2 = case StartResp of - undefined -> fun json_view_start_resp/6; - _ -> StartResp - end, - - SendRow2 = case SendRow of - undefined -> fun send_json_view_row/5; - _ -> SendRow - end, - - Helpers#view_fold_helper_funs{ - start_response = StartResp2, - send_row = SendRow2 - }; - - -apply_default_helper_funs( - #reduce_fold_helper_funs{ - start_response = StartResp, - send_row = SendRow - }=Helpers) -> - StartResp2 = case StartResp of - undefined -> fun json_reduce_start_resp/4; - _ -> StartResp - end, - - SendRow2 = case SendRow of - undefined -> fun send_json_reduce_row/3; - _ -> SendRow - end, - - Helpers#reduce_fold_helper_funs{ - start_response = StartResp2, - send_row = SendRow2 - }. - -make_key_options(#view_query_args{direction = Dir}=QueryArgs) -> - [{dir,Dir} | make_start_key_option(QueryArgs) ++ - make_end_key_option(QueryArgs)]. - -make_start_key_option( - #view_query_args{ - start_key = StartKey, - start_docid = StartDocId}) -> - if StartKey == undefined -> - []; - true -> - [{start_key, {StartKey, StartDocId}}] - end. - -make_end_key_option(#view_query_args{end_key = undefined}) -> - []; -make_end_key_option( - #view_query_args{end_key = EndKey, - end_docid = EndDocId, - inclusive_end = true}) -> - [{end_key, {EndKey, EndDocId}}]; -make_end_key_option( - #view_query_args{ - end_key = EndKey, - end_docid = EndDocId, - inclusive_end = false}) -> - [{end_key_gt, {EndKey,reverse_key_default(EndDocId)}}]. - -json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc, UpdateSeq) -> - {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]), - BeginBody = case couch_httpd:qs_value(Req, "update_seq") of - "true" -> - io_lib:format( - "{\"total_rows\":~w,\"update_seq\":~w," - "\"offset\":~w,\"rows\":[\r\n", - [TotalViewCount, UpdateSeq, Offset]); - _Else -> - io_lib:format( - "{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n", - [TotalViewCount, Offset]) - end, - {ok, Resp, BeginBody}. - -send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) -> - JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs), - send_chunk(Resp, RowFront ++ ?JSON_ENCODE(JsonObj)), - {ok, ",\r\n"}. - -json_reduce_start_resp(Req, Etag, _Acc0, UpdateSeq) -> - {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]), - case couch_httpd:qs_value(Req, "update_seq") of - "true" -> - {ok, Resp, io_lib:format("{\"update_seq\":~w,\"rows\":[\r\n",[UpdateSeq])}; - _Else -> - {ok, Resp, "{\"rows\":[\r\n"} - end. - -send_json_reduce_row(Resp, {Key, Value}, RowFront) -> - send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})), - {ok, ",\r\n"}. - -view_group_etag(Group, Db) -> - view_group_etag(Group, Db, nil). - -view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, _Db, Extra) -> - % ?LOG_ERROR("Group ~p",[Group]), - % This is not as granular as it could be. - % If there are updates to the db that do not effect the view index, - % they will change the Etag. For more granular Etags we'd need to keep - % track of the last Db seq that caused an index change. - couch_httpd:make_etag({Sig, CurrentSeq, Extra}). - -% the view row has an error -view_row_obj(_Db, {{Key, error}, Value}, _IncludeDocs) -> - {[{key, Key}, {error, Value}]}; -% include docs in the view output -view_row_obj(Db, {{Key, DocId}, {Props}}, true) -> - Rev = case couch_util:get_value(<<"_rev">>, Props) of - undefined -> - nil; - Rev0 -> - couch_doc:parse_rev(Rev0) - end, - IncludeId = couch_util:get_value(<<"_id">>, Props, DocId), - view_row_with_doc(Db, {{Key, DocId}, {Props}}, {IncludeId, Rev}); -view_row_obj(Db, {{Key, DocId}, Value}, true) -> - view_row_with_doc(Db, {{Key, DocId}, Value}, {DocId, nil}); -% the normal case for rendering a view row -view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs) -> - {[{id, DocId}, {key, Key}, {value, Value}]}. - -view_row_with_doc(Db, {{Key, DocId}, Value}, IdRev) -> - {[{id, DocId}, {key, Key}, {value, Value}] ++ doc_member(Db, IdRev)}. - -doc_member(Db, {DocId, Rev}) -> - ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]), - case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of - #doc{} = Doc -> - JsonDoc = couch_doc:to_json_obj(Doc, []), - [{doc, JsonDoc}]; - _Else -> - [{doc, null}] - end. - -finish_view_fold(Req, TotalRows, Offset, FoldResult) -> - finish_view_fold(Req, TotalRows, Offset, FoldResult, []). - -finish_view_fold(Req, TotalRows, Offset, FoldResult, Fields) -> - case FoldResult of - {_, _, undefined, _} -> - % nothing found in the view or keys, nothing has been returned - % send empty view - send_json(Req, 200, {[ - {total_rows, TotalRows}, - {offset, Offset}, - {rows, []} - ] ++ Fields}); - {_, _, Resp, _} -> - % end the view - send_chunk(Resp, "\r\n]}"), - end_json_response(Resp) - end. - -finish_reduce_fold(Req, Resp) -> - finish_reduce_fold(Req, Resp, []). - -finish_reduce_fold(Req, Resp, Fields) -> - case Resp of - undefined -> - send_json(Req, 200, {[ - {rows, []} - ] ++ Fields}); - Resp -> - send_chunk(Resp, "\r\n]}"), - end_json_response(Resp) - end. - -parse_bool_param(Val) -> - case string:to_lower(Val) of - "true" -> true; - "false" -> false; - _ -> - Msg = io_lib:format("Invalid boolean parameter: ~p", [Val]), - throw({query_parse_error, ?l2b(Msg)}) - end. - -parse_int_param(Val) -> - case (catch list_to_integer(Val)) of - IntVal when is_integer(IntVal) -> - IntVal; - _ -> - Msg = io_lib:format("Invalid value for integer parameter: ~p", [Val]), - throw({query_parse_error, ?l2b(Msg)}) - end. - -parse_positive_int_param(Val) -> - case parse_int_param(Val) of - IntVal when IntVal >= 0 -> - IntVal; - _ -> - Fmt = "Invalid value for positive integer parameter: ~p", - Msg = io_lib:format(Fmt, [Val]), - throw({query_parse_error, ?l2b(Msg)}) - end. - -- cgit v1.2.3