summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2018-10-23 13:07:49 -0500
committerRobert Newson <rnewson@apache.org>2018-11-23 09:47:58 +0000
commit071b54ac63febd507a092a9bbf9f05813ccd7415 (patch)
treeabe5cc2d0661ed044033e2108da0166f3ed91339
parent3b5d52d6283cfa7d4ecbdf24d1fae51fe07c1833 (diff)
downloadcouchdb-071b54ac63febd507a092a9bbf9f05813ccd7415.tar.gz
[4/5] Implement HTTP layer for partitioned views
-rw-r--r--src/chttpd/src/chttpd_db.erl88
-rw-r--r--src/chttpd/src/chttpd_handlers.erl10
-rw-r--r--src/chttpd/src/chttpd_httpd_handlers.erl8
-rw-r--r--src/chttpd/src/chttpd_view.erl39
-rw-r--r--src/chttpd/test/chttpd_db_bulk_get_test.erl33
-rw-r--r--src/couch_mrview/src/couch_mrview_http.erl3
-rw-r--r--src/couch_mrview/src/couch_mrview_util.erl14
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;