diff options
Diffstat (limited to 'src/couchdb/couch_httpd_show.erl')
-rw-r--r-- | src/couchdb/couch_httpd_show.erl | 164 |
1 files changed, 93 insertions, 71 deletions
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index 467c0a42..70dd82e1 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -13,7 +13,7 @@ -module(couch_httpd_show). -export([handle_doc_show_req/3, handle_doc_update_req/3, handle_view_list_req/3, - handle_doc_show/5, handle_view_list/6, get_fun_key/3]). + handle_view_list/6, get_fun_key/3]). -include("couch_db.hrl"). @@ -22,18 +22,42 @@ start_json_response/2,send_chunk/2,last_chunk/1,send_chunked_error/2, start_chunked_response/3, send_error/4]). -% /db/_design/foo/show/bar/docid -% show converts a json doc to a response of any content-type. + +% /db/_design/foo/_show/bar/docid +% show converts a json doc to a response of any content-type. % it looks up the doc an then passes it to the query server. % then it sends the response from the query server to the http client. + +maybe_open_doc(Db, DocId) -> + case catch couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of + {not_found, missing} -> nil; + {not_found,deleted} -> nil; + Doc -> Doc + end. handle_doc_show_req(#httpd{ path_parts=[_, _, _, _, ShowName, DocId] }=Req, Db, DDoc) -> + % open the doc - Doc = couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]), + Doc = maybe_open_doc(Db, DocId), + % we don't handle revs here b/c they are an internal api % returns 404 if there is no doc with DocId - handle_doc_show(Req, Db, DDoc, ShowName, Doc); + handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId); + +handle_doc_show_req(#httpd{ + path_parts=[_, _, _, _, ShowName, DocId|Rest] + }=Req, Db, DDoc) -> + + DocParts = [DocId|Rest], + DocId1 = ?l2b(string:join([?b2l(P)|| P <- DocParts], "/")), + + % open the doc + Doc = maybe_open_doc(Db, DocId1), + + % we don't handle revs here b/c they are an internal api + % pass 404 docs to the show function + handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId1); handle_doc_show_req(#httpd{ path_parts=[_, _, _, _, ShowName] @@ -45,12 +69,15 @@ handle_doc_show_req(Req, _Db, _DDoc) -> send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>). handle_doc_show(Req, Db, DDoc, ShowName, Doc) -> + handle_doc_show(Req, Db, DDoc, ShowName, Doc, null). + +handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId) -> % get responder for ddoc/showname CurrentEtag = show_etag(Req, Doc, DDoc, []), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - JsonReq = couch_httpd_external:json_req_obj(Req, Db), + JsonReq = couch_httpd_external:json_req_obj(Req, Db, DocId), JsonDoc = couch_query_servers:json_doc(Doc), - [<<"resp">>, ExternalResp] = + [<<"resp">>, ExternalResp] = couch_query_servers:ddoc_prompt(DDoc, [<<"shows">>, ShowName], [JsonDoc, JsonReq]), JsonResp = apply_etag(ExternalResp, CurrentEtag), couch_httpd_external:send_external_response(Req, JsonResp) @@ -68,7 +95,7 @@ show_etag(#httpd{user_ctx=UserCtx}=Req, Doc, DDoc, More) -> get_fun_key(DDoc, Type, Name) -> #doc{body={Props}} = DDoc, - Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), + Lang = couch_util:get_value(<<"language">>, Props, <<"javascript">>), Src = couch_util:get_nested_json_value({Props}, [Type, Name]), {Lang, Src}. @@ -98,22 +125,27 @@ handle_doc_update_req(Req, _Db, _DDoc) -> send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) -> JsonReq = couch_httpd_external:json_req_obj(Req, Db, DocId), JsonDoc = couch_query_servers:json_doc(Doc), - case couch_query_servers:ddoc_prompt(DDoc, [<<"updates">>, UpdateName], [JsonDoc, JsonReq]) of - [<<"up">>, {NewJsonDoc}, JsonResp] -> - Options = case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of + {Code, JsonResp1} = case couch_query_servers:ddoc_prompt(DDoc, + [<<"updates">>, UpdateName], [JsonDoc, JsonReq]) of + [<<"up">>, {NewJsonDoc}, {JsonResp}] -> + Options = case couch_httpd:header_value(Req, "X-Couch-Full-Commit", + "false") of "true" -> [full_commit]; _ -> [] end, NewDoc = couch_doc:from_json_obj({NewJsonDoc}), - Code = 201, - {ok, _NewRev} = couch_db:update_doc(Db, NewDoc, Options); + {ok, NewRev} = couch_db:update_doc(Db, NewDoc, Options), + NewRevStr = couch_doc:rev_to_str(NewRev), + JsonRespWithRev = {[{<<"headers">>, + {[{<<"X-Couch-Update-NewRev">>, NewRevStr}]}} | JsonResp]}, + {201, JsonRespWithRev}; [<<"up">>, _Other, JsonResp] -> - Code = 200, - ok + {200, JsonResp} end, - JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp), + + JsonResp2 = couch_util:json_apply_field({<<"code">>, Code}, JsonResp1), % todo set location field couch_httpd_external:send_external_response(Req, JsonResp2). @@ -121,12 +153,14 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) -> % view-list request with view and list from same design doc. handle_view_list_req(#httpd{method='GET', path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc) -> - handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, nil); + Keys = couch_httpd:qs_json_value(Req, "keys", nil), + handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, Keys); % view-list request with view and list from different design docs. handle_view_list_req(#httpd{method='GET', path_parts=[_, _, _, _, ListName, ViewDesignName, ViewName]}=Req, Db, DDoc) -> - handle_view_list(Req, Db, DDoc, ListName, {ViewDesignName, ViewName}, nil); + Keys = couch_httpd:qs_json_value(Req, "keys", nil), + handle_view_list(Req, Db, DDoc, ListName, {ViewDesignName, ViewName}, Keys); handle_view_list_req(#httpd{method='GET'}=Req, _Db, _DDoc) -> send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>); @@ -136,7 +170,7 @@ handle_view_list_req(#httpd{method='POST', % {Props2} = couch_httpd:json_body(Req), ReqBody = couch_httpd:body(Req), {Props2} = ?JSON_DECODE(ReqBody), - Keys = proplists:get_value(<<"keys">>, Props2, nil), + Keys = couch_util:get_value(<<"keys">>, Props2, nil), handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName, {DesignName, ViewName}, Keys); handle_view_list_req(#httpd{method='POST', @@ -144,7 +178,7 @@ handle_view_list_req(#httpd{method='POST', % {Props2} = couch_httpd:json_body(Req), ReqBody = couch_httpd:body(Req), {Props2} = ?JSON_DECODE(ReqBody), - Keys = proplists:get_value(<<"keys">>, Props2, nil), + Keys = couch_util:get_value(<<"keys">>, Props2, nil), handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName, {ViewDesignName, ViewName}, Keys); handle_view_list_req(#httpd{method='POST'}=Req, _Db, _DDoc) -> @@ -156,23 +190,23 @@ handle_view_list_req(Req, _Db, _DDoc) -> handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) -> ViewDesignId = <<"_design/", ViewDesignName/binary>>, {ViewType, View, Group, QueryArgs} = couch_httpd_view:load_view(Req, Db, {ViewDesignId, ViewName}, Keys), - Etag = list_etag(Req, Db, Group, {couch_httpd:doc_etag(DDoc), Keys}), + Etag = list_etag(Req, Db, Group, View, {couch_httpd:doc_etag(DDoc), Keys}), couch_httpd:etag_respond(Req, Etag, fun() -> - output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) - end). + output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) + end). -list_etag(#httpd{user_ctx=UserCtx}=Req, Db, Group, More) -> +list_etag(#httpd{user_ctx=UserCtx}=Req, Db, Group, View, More) -> Accept = couch_httpd:header_value(Req, "Accept"), - couch_httpd_view:view_group_etag(Group, Db, {More, Accept, UserCtx#user_ctx.roles}). + couch_httpd_view:view_etag(Db, Group, View, {More, Accept, UserCtx#user_ctx.roles}). -output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> - output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys); -output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> - output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys). +output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> + output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group); +output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> + output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group). % next step: % use with_ddoc_proc/2 to make this simpler -output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> +output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> #view_query_args{ limit = Limit, skip = SkipCount @@ -188,12 +222,13 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> reduce_count = fun couch_view:reduce_to_count/1, start_response = StartListRespFun = make_map_start_resp_fun(QServer, Db, LName), send_row = make_map_send_row_fun(QServer) - }, + }, + CurrentSeq = Group#group.current_seq, {ok, _, FoldResult} = case Keys of nil -> - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, RowCount, ListFoldHelpers), - couch_view:fold(View, FoldlFun, FoldAccInit, + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers), + couch_view:fold(View, FoldlFun, FoldAccInit, couch_httpd_view:make_key_options(QueryArgs)); Keys -> lists:foldl( @@ -202,27 +237,29 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> start_key = Key, end_key = Key }, - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, RowCount, ListFoldHelpers), + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers), couch_view:fold(View, FoldlFun, FoldAcc, couch_httpd_view:make_key_options(QueryArgs2)) end, {ok, nil, FoldAccInit}, Keys) end, - finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, RowCount) + finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, CurrentSeq, RowCount) end). -output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> +output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> #view_query_args{ limit = Limit, skip = SkipCount, group_level = GroupLevel } = QueryArgs, + CurrentSeq = Group#group.current_seq, + couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) -> StartListRespFun = make_reduce_start_resp_fun(QServer, Db, LName), SendListRowFun = make_reduce_send_row_fun(QServer, Db), {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req, - GroupLevel, QueryArgs, Etag, + GroupLevel, QueryArgs, Etag, CurrentSeq, #reduce_fold_helper_funs{ start_response = StartListRespFun, send_row = SendListRowFun @@ -230,36 +267,36 @@ output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> FoldAccInit = {Limit, SkipCount, undefined, []}, {ok, FoldResult} = case Keys of nil -> - couch_view:fold_reduce(View, RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} | + couch_view:fold_reduce(View, RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} | couch_httpd_view:make_key_options(QueryArgs)]); Keys -> lists:foldl( fun(Key, {ok, FoldAcc}) -> couch_view:fold_reduce(View, RespFun, FoldAcc, - [{key_group_fun, GroupRowsFun} | + [{key_group_fun, GroupRowsFun} | couch_httpd_view:make_key_options( QueryArgs#view_query_args{start_key=Key, end_key=Key})] - ) + ) end, {ok, FoldAccInit}, Keys) end, - finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, null) + finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, CurrentSeq, null) end). make_map_start_resp_fun(QueryServer, Db, LName) -> - fun(Req, Etag, TotalRows, Offset, _Acc) -> - Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]}, + fun(Req, Etag, TotalRows, Offset, _Acc, UpdateSeq) -> + Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}, {<<"update_seq">>, UpdateSeq}]}, start_list_resp(QueryServer, LName, Req, Db, Head, Etag) end. make_reduce_start_resp_fun(QueryServer, Db, LName) -> - fun(Req2, Etag, _Acc) -> - start_list_resp(QueryServer, LName, Req2, Db, {[]}, Etag) + fun(Req2, Etag, _Acc, UpdateSeq) -> + start_list_resp(QueryServer, LName, Req2, Db, {[{<<"update_seq">>, UpdateSeq}]}, Etag) end. start_list_resp(QServer, LName, Req, Db, Head, Etag) -> JsonReq = couch_httpd_external:json_req_obj(Req, Db), - [<<"start">>,Chunks,JsonResp] = couch_query_servers:ddoc_proc_prompt(QServer, + [<<"start">>,Chunks,JsonResp] = couch_query_servers:ddoc_proc_prompt(QServer, [<<"lists">>, LName], [Head, JsonReq]), JsonResp2 = apply_etag(JsonResp, Etag), #extern_resp_args{ @@ -313,7 +350,7 @@ send_non_empty_chunk(Resp, Chunk) -> _ -> send_chunk(Resp, Chunk) end. -finish_list(Req, {Proc, _DDocId}, Etag, FoldResult, StartFun, TotalRows) -> +finish_list(Req, {Proc, _DDocId}, Etag, FoldResult, StartFun, CurrentSeq, TotalRows) -> FoldResult2 = case FoldResult of {Limit, SkipCount, Response, RowAcc} -> {Limit, SkipCount, Response, RowAcc, nil}; @@ -323,8 +360,8 @@ finish_list(Req, {Proc, _DDocId}, Etag, FoldResult, StartFun, TotalRows) -> case FoldResult2 of {_, _, undefined, _, _} -> {ok, Resp, BeginBody} = - render_head_for_empty_list(StartFun, Req, Etag, TotalRows), - [<<"end">>, Chunks] = couch_query_servers:proc_prompt(Proc, [<<"list_end">>]), + render_head_for_empty_list(StartFun, Req, Etag, CurrentSeq, TotalRows), + [<<"end">>, Chunks] = couch_query_servers:proc_prompt(Proc, [<<"list_end">>]), Chunk = BeginBody ++ ?b2l(?l2b(Chunks)), send_non_empty_chunk(Resp, Chunk); {_, _, Resp, stop, _} -> @@ -336,32 +373,17 @@ finish_list(Req, {Proc, _DDocId}, Etag, FoldResult, StartFun, TotalRows) -> last_chunk(Resp). -render_head_for_empty_list(StartListRespFun, Req, Etag, null) -> - StartListRespFun(Req, Etag, []); % for reduce -render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) -> - StartListRespFun(Req, Etag, TotalRows, null, []). - - -% Maybe this is in the proplists API -% todo move to couch_util -json_apply_field(H, {L}) -> - json_apply_field(H, L, []). -json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> - % drop matching keys - json_apply_field({Key, NewValue}, Headers, Acc); -json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> - % something else is next, leave it alone. - json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); -json_apply_field({Key, NewValue}, [], Acc) -> - % end of list, add ours - {[{Key, NewValue}|Acc]}. +render_head_for_empty_list(StartListRespFun, Req, Etag, CurrentSeq, null) -> + StartListRespFun(Req, Etag, [], CurrentSeq); % for reduce +render_head_for_empty_list(StartListRespFun, Req, Etag, CurrentSeq, TotalRows) -> + StartListRespFun(Req, Etag, TotalRows, null, [], CurrentSeq). apply_etag({ExternalResponse}, CurrentEtag) -> % Here we embark on the delicate task of replacing or creating the % headers on the JsonResponse object. We need to control the Etag and % Vary headers. If the external function controls the Etag, we'd have to % run it to check for a match, which sort of defeats the purpose. - case proplists:get_value(<<"headers">>, ExternalResponse, nil) of + case couch_util:get_value(<<"headers">>, ExternalResponse, nil) of nil -> % no JSON headers % add our Etag and Vary headers to the response @@ -369,8 +391,8 @@ apply_etag({ExternalResponse}, CurrentEtag) -> JsonHeaders -> {[case Field of {<<"headers">>, JsonHeaders} -> % add our headers - JsonHeadersEtagged = json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders), - JsonHeadersVaried = json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), + JsonHeadersEtagged = couch_util:json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders), + JsonHeadersVaried = couch_util:json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), {<<"headers">>, JsonHeadersVaried}; _ -> % skip non-header fields Field |