blob: 32b646960dd69f131c42710308b52fda3402e9c8 [file] [log] [blame]
% 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(chttpd_view).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch_mrview/include/couch_mrview.hrl").
-export([handle_view_req/3, handle_temp_view_req/2]).
multi_query_view(Req, Db, DDoc, ViewName, Queries) ->
Args0 = couch_mrview_http:parse_params(Req, undefined),
{ok, #mrst{views=Views}} = couch_mrview_util:ddoc_to_mrst(Db, DDoc),
Args1 = couch_mrview_util:set_view_type(Args0, ViewName, Views),
ArgQueries = lists:map(fun({Query}) ->
QueryArg = couch_mrview_http:parse_params(Query, undefined,
Args1, [decoded]),
QueryArg1 = couch_mrview_util:set_view_type(QueryArg, ViewName, Views),
couch_mrview_util:validate_args(QueryArg1)
end, Queries),
VAcc0 = #vacc{db=Db, req=Req, prepend="\r\n"},
FirstChunk = "{\"results\":[",
{ok, Resp0} = chttpd:start_delayed_json_response(VAcc0#vacc.req, 200, [], FirstChunk),
VAcc1 = VAcc0#vacc{resp=Resp0},
VAcc2 = lists:foldl(fun(Args, Acc0) ->
{ok, Acc1} = fabric:query_view(Db, DDoc, ViewName, fun couch_mrview_http:view_cb/2, Acc0, Args),
Acc1
end, VAcc1, ArgQueries),
{ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"),
chttpd:end_delayed_json_response(Resp1).
design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
Args = couch_mrview_http:parse_params(Req, Keys),
Max = chttpd:chunked_response_buffer_size(),
VAcc = #vacc{db=Db, req=Req, threshold=Max},
{ok, Resp} = fabric:query_view(Db, DDoc, ViewName, fun couch_mrview_http:view_cb/2, VAcc, Args),
{ok, Resp#vacc.resp}.
handle_view_req(#httpd{method='GET',
path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
couch_stats:increment_counter([couchdb, httpd, view_reads]),
Keys = chttpd:qs_json_value(Req, "keys", undefined),
design_doc_view(Req, Db, DDoc, ViewName, Keys);
handle_view_req(#httpd{method='POST',
path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
chttpd:validate_ctype(Req, "application/json"),
Props = couch_httpd:json_body_obj(Req),
Keys = couch_mrview_util:get_view_keys(Props),
Queries = couch_mrview_util:get_view_queries(Props),
case {Queries, Keys} of
{Queries, undefined} when is_list(Queries) ->
[couch_stats:increment_counter([couchdb, httpd, view_reads]) || _I <- Queries],
multi_query_view(Req, Db, DDoc, ViewName, Queries);
{undefined, Keys} when is_list(Keys) ->
couch_stats:increment_counter([couchdb, httpd, view_reads]),
design_doc_view(Req, Db, DDoc, ViewName, Keys);
{undefined, undefined} ->
throw({
bad_request,
"POST body must contain `keys` or `queries` field"
});
{_, _} ->
throw({bad_request, "`keys` and `queries` are mutually exclusive"})
end;
handle_view_req(Req, _Db, _DDoc) ->
chttpd:send_method_not_allowed(Req, "GET,POST,HEAD").
handle_temp_view_req(Req, _Db) ->
Msg = <<"Temporary views are not supported in CouchDB">>,
chttpd:send_error(Req, 403, forbidden, Msg).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
check_multi_query_reduce_view_overrides_test_() ->
{
foreach,
fun setup/0,
fun teardown/1,
[
t_check_include_docs_throw_validation_error(),
t_check_user_can_override_individual_query_type()
]
}.
t_check_include_docs_throw_validation_error() ->
?_test(begin
Req = #httpd{qs = []},
Query = {[{<<"include_docs">>, true}]},
Throw = {query_parse_error, <<"`include_docs` is invalid for reduce">>},
?assertThrow(Throw, multi_query_view(Req, db, ddoc, <<"v">>, [Query]))
end).
t_check_user_can_override_individual_query_type() ->
?_test(begin
Req = #httpd{qs = []},
Query = {[{<<"include_docs">>, true}, {<<"reduce">>, false}]},
multi_query_view(Req, db, ddoc, <<"v">>, [Query]),
?assertEqual(1, meck:num_calls(chttpd, start_delayed_json_response, '_'))
end).
setup() ->
Views = [#mrview{reduce_funs = [{<<"v">>, <<"_count">>}]}],
meck:expect(couch_mrview_util, ddoc_to_mrst, 2, {ok, #mrst{views = Views}}),
meck:expect(chttpd, start_delayed_json_response, 4, {ok, resp}),
meck:expect(fabric, query_view, 6, {ok, #vacc{}}),
meck:expect(chttpd, send_delayed_chunk, 2, {ok, resp}),
meck:expect(chttpd, end_delayed_json_response, 1, ok).
teardown(_) ->
meck:unload().
-endif.