summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_httpd_show.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_httpd_show.erl')
-rw-r--r--src/couchdb/couch_httpd_show.erl154
1 files changed, 87 insertions, 67 deletions
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index 467c0a42..d50ca83a 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).
@@ -136,7 +168,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 +176,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 +188,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, {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) ->
Accept = couch_httpd:header_value(Req, "Accept"),
couch_httpd_view:view_group_etag(Group, Db, {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 +220,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 +235,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 +265,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 +348,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 +358,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 +371,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 +389,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