| % 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), |
| fabric_util:validate_args(Db, DDoc, QueryArg1) |
| end, |
| Queries |
| ), |
| Options = [{user_ctx, Req#httpd.user_ctx}], |
| 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, |
| Options, |
| DDoc, |
| ViewName, |
| fun 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_post_view(Req, Props, Db, DDoc, ViewName, Keys) -> |
| Args = couch_mrview_http:parse_body_and_query(Req, Props, Keys), |
| fabric_query_view(Db, Req, DDoc, ViewName, Args). |
| |
| design_doc_view(Req, Db, DDoc, ViewName, Keys) -> |
| Args = couch_mrview_http:parse_params(Req, Keys), |
| fabric_query_view(Db, Req, DDoc, ViewName, Args). |
| |
| fabric_query_view(Db, Req, DDoc, ViewName, Args) -> |
| Max = chttpd:chunked_response_buffer_size(), |
| VAcc = #vacc{db = Db, req = Req, threshold = Max}, |
| Options = [{user_ctx, Req#httpd.user_ctx}], |
| {ok, Resp} = fabric:query_view( |
| Db, |
| Options, |
| DDoc, |
| ViewName, |
| fun view_cb/2, |
| VAcc, |
| Args |
| ), |
| {ok, Resp#vacc.resp}. |
| |
| view_cb({row, Row} = Msg, Acc) -> |
| case lists:keymember(doc, 1, Row) of |
| true -> chttpd_stats:incr_reads(); |
| false -> ok |
| end, |
| chttpd_stats:incr_rows(), |
| couch_mrview_http:view_cb(Msg, Acc); |
| view_cb(Msg, Acc) -> |
| couch_mrview_http:view_cb(Msg, Acc). |
| |
| handle_view_req( |
| #httpd{ |
| method = 'POST', |
| path_parts = [_, _, _, _, ViewName, <<"queries">>] |
| } = Req, |
| Db, |
| DDoc |
| ) -> |
| chttpd:validate_ctype(Req, "application/json"), |
| Props = couch_httpd:json_body_obj(Req), |
| case couch_mrview_util:get_view_queries(Props) of |
| undefined -> |
| throw({bad_request, <<"POST body must include `queries` parameter.">>}); |
| Queries -> |
| multi_query_view(Req, Db, DDoc, ViewName, Queries) |
| end; |
| handle_view_req( |
| #httpd{path_parts = [_, _, _, _, _, <<"queries">>]} = Req, |
| _Db, |
| _DDoc |
| ) -> |
| chttpd:send_method_not_allowed(Req, "POST"); |
| 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), |
| assert_no_queries_param(couch_mrview_util:get_view_queries(Props)), |
| Keys = couch_mrview_util:get_view_keys(Props), |
| couch_stats:increment_counter([couchdb, httpd, view_reads]), |
| design_doc_post_view(Req, Props, Db, DDoc, ViewName, Keys); |
| 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, 410, gone, Msg). |
| |
| % See https://github.com/apache/couchdb/issues/2168 |
| assert_no_queries_param(undefined) -> |
| ok; |
| assert_no_queries_param(_) -> |
| throw({ |
| bad_request, |
| "The `queries` parameter is no longer supported at this endpoint" |
| }). |
| |
| -ifdef(TEST). |
| |
| -include_lib("eunit/include/eunit.hrl"). |
| |
| check_multi_query_reduce_view_overrides_test_() -> |
| { |
| setup, |
| fun setup_all/0, |
| fun teardown_all/1, |
| { |
| 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 = []}, |
| Db = test_util:fake_db([{name, <<"foo">>}]), |
| 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 = []}, |
| Db = test_util:fake_db([{name, <<"foo">>}]), |
| 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_all() -> |
| 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, 7, {ok, #vacc{}}), |
| meck:expect(chttpd, send_delayed_chunk, 2, {ok, resp}), |
| meck:expect(chttpd, end_delayed_json_response, 1, ok). |
| |
| teardown_all(_) -> |
| meck:unload(). |
| |
| setup() -> |
| meck:reset([ |
| chttpd, |
| couch_mrview_util, |
| fabric |
| ]). |
| |
| teardown(_) -> |
| ok. |
| |
| -endif. |