diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2018-10-23 13:07:49 -0500 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2018-11-23 09:47:58 +0000 |
commit | 071b54ac63febd507a092a9bbf9f05813ccd7415 (patch) | |
tree | abe5cc2d0661ed044033e2108da0166f3ed91339 | |
parent | 3b5d52d6283cfa7d4ecbdf24d1fae51fe07c1833 (diff) | |
download | couchdb-071b54ac63febd507a092a9bbf9f05813ccd7415.tar.gz |
[4/5] Implement HTTP layer for partitioned views
-rw-r--r-- | src/chttpd/src/chttpd_db.erl | 88 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_handlers.erl | 10 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_httpd_handlers.erl | 8 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_view.erl | 39 | ||||
-rw-r--r-- | src/chttpd/test/chttpd_db_bulk_get_test.erl | 33 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_http.erl | 3 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_util.erl | 14 |
7 files changed, 153 insertions, 42 deletions
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl index 9b2d52e37..ca9f10c33 100644 --- a/src/chttpd/src/chttpd_db.erl +++ b/src/chttpd/src/chttpd_db.erl @@ -18,7 +18,8 @@ db_req/2, couch_doc_open/4,handle_changes_req/2, update_doc_result_to_json/1, update_doc_result_to_json/2, handle_design_info_req/3, handle_view_cleanup_req/2, - update_doc/4, http_code_from_status/1]). + update_doc/4, http_code_from_status/1, + handle_partition_req/2]). -import(chttpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, @@ -250,21 +251,77 @@ handle_view_cleanup_req(Req, Db) -> ok = fabric:cleanup_index_files_all_nodes(Db), send_json(Req, 202, {[{ok, true}]}). + +handle_partition_req(#httpd{method='GET',path_parts=[DbName, <<"_partition">>, Partition]}=Req, _Db) -> + {ok, PartitionInfo} = fabric:get_partition_info(DbName, Partition), + send_json(Req, {PartitionInfo}); + +handle_partition_req(#httpd{ + path_parts=[DbName, <<"_partition">>, Partition, _Design, Name, <<"_",_/binary>> = Action | _Rest] + }=Req, Db) -> + + validate_partition_req(Req, Partition, DbName), + DDoc = get_design_doc(DbName, Name), + Partitioned = couch_mrview:get_partitioned_opt(DDoc#doc.body, true), + + case Partitioned of + true -> + Handler = chttpd_handlers:partition_design_handler(Action, fun bad_action_partition_design_req/4), + Handler(Req, Db, DDoc, Partition); + false -> + throw({bad_request, <<"partition query is not supported in this design doc.">>}) + end; + +handle_partition_req(#httpd{ + path_parts=[DbName, <<"_partition">>, Partition, Action | _Rest] + }=Req, Db) -> + validate_partition_req(Req, Partition, DbName), + Handler = chttpd_handlers:partition_handler(Action, fun bad_action_partition_req/3), + Handler(Req, Db, Partition); + +handle_partition_req(_Req, _Db) -> + throw({bad_request, <<"missing partition key">>}). + + +bad_action_partition_design_req(Req, _Db, _DDoc, _PartitionKey) -> + chttpd:send_error(Req, 404, <<"partition_error">>, <<"Invalid path.">>). + + +bad_action_partition_req(Req, _Db, _PartitionKey) -> + chttpd:send_error(Req, 404, <<"partition_error">>, <<"Invalid path.">>). + + +validate_partition_req(_Req, Partition, DbName) -> + % validate that the partition is a valid non-partitioned doc id. + couch_doc:validate_docid(Partition, DbName), + + case mem3:is_partitioned(DbName) of + false -> throw({bad_request, <<"Database is not partitioned">>}); + true -> ok + end. + + handle_design_req(#httpd{ path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest] }=Req, Db) -> DbName = mem3:dbname(couch_db:name(Db)), - case ddoc_cache:open(DbName, <<"_design/", Name/binary>>) of - {ok, DDoc} -> - Handler = chttpd_handlers:design_handler(Action, fun bad_action_req/3), - Handler(Req, Db, DDoc); - Error -> - throw(Error) - end; + DDoc = get_design_doc(DbName, Name), + Handler = chttpd_handlers:design_handler(Action, fun bad_action_req/3), + Handler(Req, Db, DDoc); handle_design_req(Req, Db) -> db_req(Req, Db). + +get_design_doc(DbName, Name) -> + case ddoc_cache:open(DbName, <<"_design/", Name/binary>>) of + {ok, DDoc} -> + DDoc; + Error -> + throw(Error) + end. + + bad_action_req(#httpd{path_parts=[_, _, Name|FileNameParts]}=Req, Db, _DDoc) -> db_attachment_req(Req, Db, <<"_design/",Name/binary>>, FileNameParts). @@ -718,15 +775,18 @@ multi_all_docs_view(Req, Db, OP, Queries) -> {ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"), chttpd:end_delayed_json_response(Resp1). -all_docs_view(Req, Db, Keys, OP) -> +all_docs_view(#httpd{path_parts=[DbName | _]}=Req, Db, Keys, OP) -> + Partitioned = mem3:is_partitioned(DbName), Args0 = couch_mrview_http:parse_params(Req, Keys), Args1 = Args0#mrargs{view_type=map}, - Args2 = couch_mrview_util:validate_args(Args1), - Args3 = set_namespace(OP, Args2), + Args2 = couch_mrview_util:set_extra(Args1, style, all_docs), + Args3 = couch_mrview_util:set_extra(Args2, partitioned, Partitioned), + Args4 = couch_mrview_util:validate_args(Args3), + Args5 = set_namespace(OP, Args4), Options = [{user_ctx, Req#httpd.user_ctx}], Max = chttpd:chunked_response_buffer_size(), VAcc = #vacc{db=Db, req=Req, threshold=Max}, - {ok, Resp} = fabric:all_docs(Db, Options, fun couch_mrview_http:view_cb/2, VAcc, Args3), + {ok, Resp} = fabric:all_docs(Db, Options, fun couch_mrview_http:view_cb/2, VAcc, Args5), {ok, Resp#vacc.resp}. db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> @@ -1672,8 +1732,8 @@ set_namespace(<<"_local_docs">>, Args) -> set_namespace(<<"_local">>, Args); set_namespace(<<"_design_docs">>, Args) -> set_namespace(<<"_design">>, Args); -set_namespace(NS, #mrargs{extra = Extra} = Args) -> - Args#mrargs{extra = [{namespace, NS} | Extra]}. +set_namespace(NS, #mrargs{} = Args) -> + couch_mrview_util:set_extra(Args, namespace, NS). %% /db/_bulk_get stuff diff --git a/src/chttpd/src/chttpd_handlers.erl b/src/chttpd/src/chttpd_handlers.erl index 930563230..f2098bef2 100644 --- a/src/chttpd/src/chttpd_handlers.erl +++ b/src/chttpd/src/chttpd_handlers.erl @@ -15,7 +15,9 @@ -export([ url_handler/2, db_handler/2, - design_handler/2 + design_handler/2, + partition_handler/2, + partition_design_handler/2 ]). -define(SERVICE_ID, chttpd_handlers). @@ -35,6 +37,12 @@ db_handler(HandlerKey, DefaultFun) -> design_handler(HandlerKey, DefaultFun) -> select(collect(design_handler, [HandlerKey]), DefaultFun). +partition_handler(HandlerKey, DefaultFun) -> + select(collect(partition_handler, [HandlerKey]), DefaultFun). + +partition_design_handler(HandlerKey, DefaultFun) -> + select(collect(partition_design_handler, [HandlerKey]), DefaultFun). + %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl b/src/chttpd/src/chttpd_httpd_handlers.erl index cb52e2c40..e4cab7ced 100644 --- a/src/chttpd/src/chttpd_httpd_handlers.erl +++ b/src/chttpd/src/chttpd_httpd_handlers.erl @@ -12,7 +12,7 @@ -module(chttpd_httpd_handlers). --export([url_handler/1, db_handler/1, design_handler/1]). +-export([url_handler/1, db_handler/1, design_handler/1, partition_design_handler/1, partition_handler/1]). url_handler(<<>>) -> fun chttpd_misc:handle_welcome_req/1; url_handler(<<"favicon.ico">>) -> fun chttpd_misc:handle_favicon_req/1; @@ -32,6 +32,7 @@ url_handler(_) -> no_match. db_handler(<<"_view_cleanup">>) -> fun chttpd_db:handle_view_cleanup_req/2; db_handler(<<"_compact">>) -> fun chttpd_db:handle_compact_req/2; db_handler(<<"_design">>) -> fun chttpd_db:handle_design_req/2; +db_handler(<<"_partition">>) -> fun chttpd_db:handle_partition_req/2; db_handler(<<"_temp_view">>) -> fun chttpd_view:handle_temp_view_req/2; db_handler(<<"_changes">>) -> fun chttpd_db:handle_changes_req/2; db_handler(_) -> no_match. @@ -43,3 +44,8 @@ design_handler(<<"_update">>) -> fun chttpd_show:handle_doc_update_req/3; design_handler(<<"_info">>) -> fun chttpd_db:handle_design_info_req/3; design_handler(<<"_rewrite">>) -> fun chttpd_rewrite:handle_rewrite_req/3; design_handler(_) -> no_match. + +partition_design_handler(<<"_view">>) -> fun chttpd_view:handle_partition_view_req/4; +partition_design_handler(_) -> no_match. + +partition_handler(_) -> no_match. diff --git a/src/chttpd/src/chttpd_view.erl b/src/chttpd/src/chttpd_view.erl index 78e619983..0b7c3e127 100644 --- a/src/chttpd/src/chttpd_view.erl +++ b/src/chttpd/src/chttpd_view.erl @@ -14,15 +14,17 @@ -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]). +-export([handle_view_req/3, handle_temp_view_req/2, handle_partition_view_req/4]). 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), + % We don't support multi query in partitions + Args2 = couch_mrview_util:set_extra(Args1, partitioned, false), ArgQueries = lists:map(fun({Query}) -> QueryArg = couch_mrview_http:parse_params(Query, undefined, - Args1, [decoded]), + Args2, [decoded]), QueryArg1 = couch_mrview_util:set_view_type(QueryArg, ViewName, Views), couch_mrview_util:validate_args(QueryArg1) end, Queries), @@ -40,8 +42,17 @@ multi_query_view(Req, Db, DDoc, ViewName, Queries) -> chttpd:end_delayed_json_response(Resp1). -design_doc_view(Req, Db, DDoc, ViewName, Keys) -> - Args = couch_mrview_http:parse_params(Req, Keys), +design_doc_view(#httpd{path_parts=[DbName | _]}=Req, Db, DDoc, ViewName, Keys) -> + Args0 = couch_mrview_http:parse_params(Req, Keys), + {Props} = DDoc#doc.body, + {Options} = couch_util:get_value(<<"options">>, Props, {[]}), + DbPartitioned = mem3:is_partitioned(DbName), + Partitioned = couch_util:get_value(<<"partitioned">>, Options, DbPartitioned), + Args = couch_mrview_util:set_extra(Args0, partitioned, Partitioned), + design_doc_view_int(Req, Db, DDoc, ViewName, Args). + + +design_doc_view_int(Req, Db, DDoc, ViewName, Args) -> Max = chttpd:chunked_response_buffer_size(), VAcc = #vacc{db=Db, req=Req, threshold=Max}, Options = [{user_ctx, Req#httpd.user_ctx}], @@ -112,6 +123,7 @@ handle_partition_view_req(#httpd{method='POST', Args0 = couch_mrview_http:parse_params(Req, Keys), Args1 = couch_mrview_util:set_extra(Args0, partition, Partition), Args2 = couch_mrview_util:set_extra(Args1, partitioned, true), + ok = check_partition_restrictions(Args2), design_doc_view_int(Req, Db, DDoc, ViewName, Args2); _ -> throw({ @@ -126,12 +138,31 @@ handle_partition_view_req(#httpd{method='GET', Args = couch_mrview_http:parse_params(Req, Keys), Args1 = couch_mrview_util:set_extra(Args, partition, Partition), Args2 = couch_mrview_util:set_extra(Args1, partitioned, true), + ok = check_partition_restrictions(Args2), design_doc_view_int(Req, Db, DDoc, ViewName, Args2); handle_partition_view_req(Req, _Db, _DDoc, _Pk) -> chttpd:send_method_not_allowed(Req, "GET"). +check_partition_restrictions(#mrargs{} = Args) -> + Restrictions = [ + {<<"include_docs">>, Args#mrargs.include_docs}, + {<<"stable">>, Args#mrargs.stable}, + {<<"conflicts">>, Args#mrargs.conflicts} + ], + lists:foreach(fun ({Param, ArgValue}) -> + case ArgValue =:= true of + true -> + Msg = [<<"`">>, Param, <<"=true` is not supported in this view.">>], + throw({bad_request, ?l2b(Msg)}); + false -> + ok + end + end, Restrictions), + ok. + + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/src/chttpd/test/chttpd_db_bulk_get_test.erl b/src/chttpd/test/chttpd_db_bulk_get_test.erl index f8921311b..b983487d9 100644 --- a/src/chttpd/test/chttpd_db_bulk_get_test.erl +++ b/src/chttpd/test/chttpd_db_bulk_get_test.erl @@ -14,6 +14,7 @@ -include_lib("couch/include/couch_eunit.hrl"). -include_lib("couch/include/couch_db.hrl"). +-include_lib("couch/src/couch_db_int.hrl"). -define(TIMEOUT, 3000). @@ -26,6 +27,7 @@ setup() -> mock(couch_stats), mock(fabric), mock(mochireq), + mock(mem3), Pid = spawn_accumulator(), Pid. @@ -38,7 +40,8 @@ teardown(Pid) -> meck:unload(couch_httpd), meck:unload(couch_stats), meck:unload(fabric), - meck:unload(mochireq). + meck:unload(mochireq), + meck:unload(mem3). bulk_get_test_() -> @@ -95,7 +98,8 @@ should_get_doc_with_all_revs(Pid) -> DocRevB = #doc{id = DocId, body = {[{<<"_rev">>, <<"1-CDE">>}]}}, mock_open_revs(all, {ok, [{ok, DocRevA}, {ok, DocRevB}]}), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), [{Result}] = get_results_from_response(Pid), ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)), @@ -115,7 +119,8 @@ should_validate_doc_with_bad_id(Pid) -> DocId = <<"_docudoc">>, Req = fake_request(DocId), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), [{Result}] = get_results_from_response(Pid), ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)), @@ -138,7 +143,8 @@ should_validate_doc_with_bad_rev(Pid) -> Rev = <<"revorev">>, Req = fake_request(DocId, Rev), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), [{Result}] = get_results_from_response(Pid), ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)), @@ -162,7 +168,8 @@ should_validate_missing_doc(Pid) -> Req = fake_request(DocId, Rev), mock_open_revs([{1,<<"revorev">>}], {ok, []}), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), [{Result}] = get_results_from_response(Pid), ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)), @@ -186,7 +193,8 @@ should_validate_bad_atts_since(Pid) -> Req = fake_request(DocId, Rev, <<"badattsince">>), mock_open_revs([{1,<<"revorev">>}], {ok, []}), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), [{Result}] = get_results_from_response(Pid), ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)), @@ -210,10 +218,11 @@ should_include_attachments_when_atts_since_specified(_) -> Req = fake_request(DocId, Rev, [<<"1-abc">>]), mock_open_revs([{1,<<"revorev">>}], {ok, []}), - chttpd_db:db_req(Req, nil), + Db = fake_db(), + chttpd_db:db_req(Req, Db), ?_assert(meck:called(fabric, open_revs, - [nil, DocId, [{1, <<"revorev">>}], + [Db, DocId, [{1, <<"revorev">>}], [{atts_since, [{1, <<"abc">>}]}, attachments]])). %% helpers @@ -233,6 +242,10 @@ fake_request(DocId, Rev, AttsSince) -> {<<"atts_since">>, AttsSince}]}]}]}). +fake_db() -> + #db{name = <<"dbName">>}. + + mock_open_revs(RevsReq0, RevsResp) -> ok = meck:expect(fabric, open_revs, fun(_, _, RevsReq1, _) -> @@ -276,6 +289,10 @@ mock(fabric) -> mock(config) -> ok = meck:new(config, [passthrough]), ok = meck:expect(config, get, fun(_, _, Default) -> Default end), + ok; +mock(mem3) -> + ok = meck:new(mem3, [passthrough]), + ok = meck:expect(mem3, is_partitioned, 1, false), ok. diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl index 004caef09..86df7968a 100644 --- a/src/couch_mrview/src/couch_mrview_http.erl +++ b/src/couch_mrview/src/couch_mrview_http.erl @@ -582,6 +582,9 @@ parse_param(Key, Val, Args, IsDecoded) -> Args#mrargs{callback=couch_util:to_binary(Val)}; "sorted" -> Args#mrargs{sorted=parse_boolean(Val)}; + "partition" -> + Partition = couch_util:to_binary(Val), + couch_mrview_util:set_extra(Args, partition, Partition); _ -> BKey = couch_util:to_binary(Key), BVal = couch_util:to_binary(Val), diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index 9e16ea621..fcdadda5f 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -642,20 +642,6 @@ validate_args(Args) -> mrverror(<<"`partition` parameter is not supported in this view.">>) end, - case {Partitioned, Args#mrargs.conflicts} of - {true, true} -> - mrverror(<<"`conflicts=true` is not supported in this view.">>); - {_, _} -> - ok - end, - - case {Partitioned, Args#mrargs.stable} of - {true, true} -> - mrverror(<<"`stable=true` is not supported in this view.">>); - {_, _} -> - ok - end, - Args1 = case {Style, Partitioned, Partition} of {all_docs, true, undefined} -> Args; |