summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBessenyei Balázs Donát <Balazs.Donat.Bessenyei@ibm.com>2019-12-02 12:31:35 +0100
committergarren smith <garren.smith@gmail.com>2019-12-05 14:53:07 +0200
commit4815eebe56f175db57f10dabf52fd1bce616cd29 (patch)
tree6c99a226e55fc00253a0c4e29716fd1912ad5081
parent87edbaecd2cdb7f9266364e5fec65e31fe1fc251 (diff)
downloadcouchdb-4815eebe56f175db57f10dabf52fd1bce616cd29.tar.gz
Allow all params to be passed via body for POST view
This change should allow users to supply all params in POST that can be supplied for GET now. This way we could avoid the ?key="foo" things that would probably cause a lot of pain for users.
-rw-r--r--src/chttpd/src/chttpd_view.erl19
-rw-r--r--src/couch_mrview/src/couch_mrview_http.erl14
-rw-r--r--src/couch_mrview/src/couch_mrview_util.erl1
-rw-r--r--test/elixir/test/view_test.exs143
4 files changed, 164 insertions, 13 deletions
diff --git a/src/chttpd/src/chttpd_view.erl b/src/chttpd/src/chttpd_view.erl
index 0d3d86d1a..50704687e 100644
--- a/src/chttpd/src/chttpd_view.erl
+++ b/src/chttpd/src/chttpd_view.erl
@@ -39,9 +39,15 @@ multi_query_view(Req, Db, DDoc, ViewName, Queries) ->
{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}],
@@ -89,16 +95,9 @@ handle_view_req(#httpd{method='POST',
chttpd:validate_ctype(Req, "application/json"),
Props = couch_httpd:json_body_obj(Req),
assert_no_queries_param(couch_mrview_util:get_view_queries(Props)),
- case couch_mrview_util:get_view_keys(Props) of
- Keys when is_list(Keys) ->
- couch_stats:increment_counter([couchdb, httpd, view_reads]),
- design_doc_view(Req, Db, DDoc, ViewName, Keys);
- _ ->
- throw({
- bad_request,
- "POST body must contain an array called `keys`"
- })
- end;
+ 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").
diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl
index 74d5ca274..69cbb7351 100644
--- a/src/couch_mrview/src/couch_mrview_http.erl
+++ b/src/couch_mrview/src/couch_mrview_http.erl
@@ -29,6 +29,7 @@
parse_int/1,
parse_pos_int/1,
prepend_val/1,
+ parse_body_and_query/3,
parse_params/2,
parse_params/3,
parse_params/4,
@@ -453,12 +454,21 @@ parse_params(Props, Keys, Args) ->
parse_params(Props, Keys, #mrargs{}=Args0, Options) ->
IsDecoded = lists:member(decoded, Options),
- % group_level set to undefined to detect if explicitly set by user
- Args1 = Args0#mrargs{keys=Keys, group=undefined, group_level=undefined},
+ Args1 = case lists:member(keep_group_level, Options) of
+ true ->
+ Args0;
+ _ ->
+ % group_level set to undefined to detect if explicitly set by user
+ Args0#mrargs{keys=Keys, group=undefined, group_level=undefined}
+ end,
lists:foldl(fun({K, V}, Acc) ->
parse_param(K, V, Acc, IsDecoded)
end, Args1, Props).
+parse_body_and_query(Req, {Props}, Keys) ->
+ Args = #mrargs{keys=Keys, group=undefined, group_level=undefined},
+ BodyArgs = parse_params(Props, Keys, Args, [decoded]),
+ parse_params(chttpd:qs(Req), Keys, BodyArgs, [keep_group_level]).
parse_param(Key, Val, Args, IsDecoded) when is_binary(Key) ->
parse_param(binary_to_list(Key), Val, Args, IsDecoded);
diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl
index d0d2b3949..e971720c9 100644
--- a/src/couch_mrview/src/couch_mrview_util.erl
+++ b/src/couch_mrview/src/couch_mrview_util.erl
@@ -1152,7 +1152,6 @@ extract_view_reduce({red, {N, _Lang, #mrview{reduce_funs=Reds}}, _Ref}) ->
get_view_keys({Props}) ->
case couch_util:get_value(<<"keys">>, Props) of
undefined ->
- couch_log:debug("POST with no keys member.", []),
undefined;
Keys when is_list(Keys) ->
Keys;
diff --git a/test/elixir/test/view_test.exs b/test/elixir/test/view_test.exs
new file mode 100644
index 000000000..5fb8c009c
--- /dev/null
+++ b/test/elixir/test/view_test.exs
@@ -0,0 +1,143 @@
+defmodule ViewTest do
+ use CouchTestCase
+
+ @moduletag :view
+
+ @moduledoc """
+ Test CouchDB /{db}/_design/{ddoc}/_view/{view}
+ """
+
+ setup_all do
+ db_name = random_db_name()
+ {:ok, _} = create_db(db_name)
+ on_exit(fn -> delete_db(db_name) end)
+
+ {:ok, _} = create_doc(
+ db_name,
+ %{
+ _id: "foo",
+ bar: "baz"
+ }
+ )
+
+ {:ok, _} = create_doc(
+ db_name,
+ %{
+ _id: "foo2",
+ bar: "baz2"
+ }
+ )
+
+ map_fun = """
+ function(doc) {
+ emit(doc._id, doc.bar);
+ }
+ """
+
+
+ body = %{
+ :docs => [
+ %{
+ _id: "_design/map",
+ views: %{
+ some: %{
+ map: map_fun
+ }
+ }
+ }
+ ]
+ }
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: body)
+ Enum.each(resp.body, &assert(&1["ok"]))
+
+ {:ok, [db_name: db_name]}
+ end
+
+ test "GET with no parameters", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_design/map/_view/some"
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 2
+ end
+
+ test "GET with one key", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ query: %{
+ :key => "\"foo\"",
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+ test "GET with multiple keys", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ query: %{
+ :keys => "[\"foo\", \"foo2\"]",
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 2
+ end
+
+ test "POST with empty body", context do
+ resp = Couch.post(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ body: %{}
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 2
+ end
+
+ test "POST with keys and limit", context do
+ resp = Couch.post(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ body: %{
+ :keys => ["foo", "foo2"],
+ :limit => 1
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+ test "POST with query parameter and JSON body", context do
+ resp = Couch.post(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => ["foo", "foo2"]
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+ test "POST edge case with colliding parameters - query takes precedence", context do
+ resp = Couch.post(
+ "/#{context[:db_name]}/_design/map/_view/some",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => ["foo", "foo2"],
+ :limit => 2
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+end