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.erl168
1 files changed, 130 insertions, 38 deletions
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index d4b4997b..47d3fb46 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -12,7 +12,7 @@
-module(couch_httpd_show).
--export([handle_doc_show_req/2]).
+-export([handle_doc_show_req/2, handle_view_list_req/2]).
-include("couch_db.hrl").
@@ -22,50 +22,142 @@
start_json_response/2,send_chunk/2,end_json_response/1,
start_chunked_response/3, send_error/4]).
-handle_doc_show_req(#httpd{method='GET',path_parts=[_, _, DesignName, ShowName, Docid]}=Req, Db) ->
+handle_doc_show_req(#httpd{
+ method='GET',
+ path_parts=[_, _, DesignName, ShowName, Docid]
+ }=Req, Db) ->
DesignId = <<"_design/", DesignName/binary>>,
- % Anyway we can dry up this error handling?
- case (catch couch_httpd_db:couch_doc_open(Db, DesignId, [], [])) of
- {not_found, missing} ->
- throw({not_found, missing_design_doc});
- {not_found, deleted} ->
- throw({not_found, deleted_design_doc});
- DesignDoc ->
- #doc{body={Props}} = DesignDoc,
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- case proplists:get_value(<<"show">>, Props, nil) of
- {DocAndViews} ->
- case proplists:get_value(<<"docs">>, DocAndViews, nil) of
- nil ->
- throw({not_found, missing_show_docs});
- {DocShows} ->
- case proplists:get_value(ShowName, DocShows, nil) of
- nil ->
- throw({not_found, missing_show_doc_function});
- ShowSrc ->
- case (catch couch_httpd_db:couch_doc_open(
- Db, Docid, [], [])) of
- {not_found, missing} ->
- throw({not_found, missing});
- {not_found, deleted} ->
- throw({not_found, deleted});
- Doc ->
- % ok we have everythign we need. let's make it happen.
- send_doc_show_response(Lang, ShowSrc, Doc, Req, Db)
- end
- end
- end;
- nil ->
- throw({not_found, missing_show})
- end
- end;
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
+ Doc = couch_httpd_db:couch_doc_open(Db, Docid, [], []),
+ send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
- send_error(Req, 404, <<"form_error">>, <<"Invalid path.">>);
+ send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
handle_doc_show_req(Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD").
+handle_view_list_req(#httpd{method='GET',path_parts=[_, _, DesignName, ListName, ViewName]}=Req, Db) ->
+ DesignId = <<"_design/", DesignName/binary>>,
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]),
+ send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db);
+
+handle_view_list_req(Req, _Db) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+
+get_nested_json_value({Props}, [Key|Keys]) ->
+ case proplists:get_value(Key, Props, nil) of
+ nil -> throw({not_found, <<"missing json key: ", Key/binary>>});
+ Value -> get_nested_json_value(Value, Keys)
+ end;
+get_nested_json_value(Value, []) ->
+ Value;
+get_nested_json_value(_NotJSONObj, _) ->
+ throw({not_found, json_mismatch}).
+
+send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
+ % TODO add etags when we get view etags
+ #view_query_args{
+ stale = Stale,
+ reduce = Reduce
+ } = QueryArgs = couch_httpd_view:parse_view_query(Req),
+ case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
+ {ok, View} ->
+ output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+ {not_found, _Reason} ->
+ case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
+ {ok, ReduceView} ->
+ case Reduce of
+ false ->
+ MapView = couch_view:extract_map_view(ReduceView),
+ output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+ _ ->
+ throw({not_implemented, reduce_view_lists})
+ end;
+ {not_found, Reason} ->
+ throw({not_found, Reason})
+ end
+ end.
+
+output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+ #view_query_args{
+ limit = Limit,
+ direction = Dir,
+ skip = SkipCount,
+ start_key = StartKey,
+ start_docid = StartDocId
+ } = QueryArgs,
+ {ok, RowCount} = couch_view:get_row_count(View),
+ Start = {StartKey, StartDocId},
+ % get the os process here
+ % pass it into the view fold with closures
+ {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+ StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
+ JsonResp = couch_query_servers:render_list_head(QueryServer,
+ Req2, Db, TotalViewCount, Offset),
+ #extern_resp_args{
+ code = Code,
+ data = BeginBody,
+ ctype = CType,
+ headers = Headers
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, binary_to_list(BeginBody)}
+ end,
+
+ SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
+ RowFront, _IncludeDocs) ->
+ JsonResp = couch_query_servers:render_list_row(QueryServer,
+ Req, Db2, {{Key, DocId}, Value}),
+ #extern_resp_args{
+ data = RowBody
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ RowFront2 = case RowFront of
+ nil -> [];
+ _ -> RowFront
+ end,
+ send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
+ end,
+
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1,
+ start_response = StartListRespFun,
+ send_row = SendListRowFun
+ }),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+
+finish_view_list(Req, Db, QueryServer, TotalRows,
+ FoldResult, StartListRespFun) ->
+ case FoldResult of
+ {ok, {_, _, undefined, _}} ->
+ {ok, Resp, BeginBody} = StartListRespFun(Req, 200, TotalRows, null),
+ JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+ #extern_resp_args{
+ data = Tail
+ } = couch_httpd_external:parse_external_response(JsonTail),
+ send_chunk(Resp, BeginBody ++ Tail),
+ send_chunk(Resp, []);
+ {ok, {_, _, Resp, _AccRevRows}} ->
+ JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+ #extern_resp_args{
+ data = Tail
+ } = couch_httpd_external:parse_external_response(JsonTail),
+ send_chunk(Resp, Tail),
+ send_chunk(Resp, []);
+ Error ->
+ throw(Error)
+ end.
+
send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
% make a term with etag-effecting Req components, but not always changing ones.