Skip to content

Commit 4815eeb

Browse files
Bessenyei Balázs Donátgarrensmith
authored andcommitted
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.
1 parent 87edbae commit 4815eeb

File tree

4 files changed

+164
-13
lines changed

4 files changed

+164
-13
lines changed

src/chttpd/src/chttpd_view.erl

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ multi_query_view(Req, Db, DDoc, ViewName, Queries) ->
3939
{ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"),
4040
chttpd:end_delayed_json_response(Resp1).
4141

42+
design_doc_post_view(Req, Props, Db, DDoc, ViewName, Keys) ->
43+
Args = couch_mrview_http:parse_body_and_query(Req, Props, Keys),
44+
fabric_query_view(Db, Req, DDoc, ViewName, Args).
4245

4346
design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
4447
Args = couch_mrview_http:parse_params(Req, Keys),
48+
fabric_query_view(Db, Req, DDoc, ViewName, Args).
49+
50+
fabric_query_view(Db, Req, DDoc, ViewName, Args) ->
4551
Max = chttpd:chunked_response_buffer_size(),
4652
VAcc = #vacc{db=Db, req=Req, threshold=Max},
4753
Options = [{user_ctx, Req#httpd.user_ctx}],
@@ -89,16 +95,9 @@ handle_view_req(#httpd{method='POST',
8995
chttpd:validate_ctype(Req, "application/json"),
9096
Props = couch_httpd:json_body_obj(Req),
9197
assert_no_queries_param(couch_mrview_util:get_view_queries(Props)),
92-
case couch_mrview_util:get_view_keys(Props) of
93-
Keys when is_list(Keys) ->
94-
couch_stats:increment_counter([couchdb, httpd, view_reads]),
95-
design_doc_view(Req, Db, DDoc, ViewName, Keys);
96-
_ ->
97-
throw({
98-
bad_request,
99-
"POST body must contain an array called `keys`"
100-
})
101-
end;
98+
Keys = couch_mrview_util:get_view_keys(Props),
99+
couch_stats:increment_counter([couchdb, httpd, view_reads]),
100+
design_doc_post_view(Req, Props, Db, DDoc, ViewName, Keys);
102101

103102
handle_view_req(Req, _Db, _DDoc) ->
104103
chttpd:send_method_not_allowed(Req, "GET,POST,HEAD").

src/couch_mrview/src/couch_mrview_http.erl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
parse_int/1,
3030
parse_pos_int/1,
3131
prepend_val/1,
32+
parse_body_and_query/3,
3233
parse_params/2,
3334
parse_params/3,
3435
parse_params/4,
@@ -453,12 +454,21 @@ parse_params(Props, Keys, Args) ->
453454

454455
parse_params(Props, Keys, #mrargs{}=Args0, Options) ->
455456
IsDecoded = lists:member(decoded, Options),
456-
% group_level set to undefined to detect if explicitly set by user
457-
Args1 = Args0#mrargs{keys=Keys, group=undefined, group_level=undefined},
457+
Args1 = case lists:member(keep_group_level, Options) of
458+
true ->
459+
Args0;
460+
_ ->
461+
% group_level set to undefined to detect if explicitly set by user
462+
Args0#mrargs{keys=Keys, group=undefined, group_level=undefined}
463+
end,
458464
lists:foldl(fun({K, V}, Acc) ->
459465
parse_param(K, V, Acc, IsDecoded)
460466
end, Args1, Props).
461467

468+
parse_body_and_query(Req, {Props}, Keys) ->
469+
Args = #mrargs{keys=Keys, group=undefined, group_level=undefined},
470+
BodyArgs = parse_params(Props, Keys, Args, [decoded]),
471+
parse_params(chttpd:qs(Req), Keys, BodyArgs, [keep_group_level]).
462472

463473
parse_param(Key, Val, Args, IsDecoded) when is_binary(Key) ->
464474
parse_param(binary_to_list(Key), Val, Args, IsDecoded);

src/couch_mrview/src/couch_mrview_util.erl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,6 @@ extract_view_reduce({red, {N, _Lang, #mrview{reduce_funs=Reds}}, _Ref}) ->
11521152
get_view_keys({Props}) ->
11531153
case couch_util:get_value(<<"keys">>, Props) of
11541154
undefined ->
1155-
couch_log:debug("POST with no keys member.", []),
11561155
undefined;
11571156
Keys when is_list(Keys) ->
11581157
Keys;

test/elixir/test/view_test.exs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
defmodule ViewTest do
2+
use CouchTestCase
3+
4+
@moduletag :view
5+
6+
@moduledoc """
7+
Test CouchDB /{db}/_design/{ddoc}/_view/{view}
8+
"""
9+
10+
setup_all do
11+
db_name = random_db_name()
12+
{:ok, _} = create_db(db_name)
13+
on_exit(fn -> delete_db(db_name) end)
14+
15+
{:ok, _} = create_doc(
16+
db_name,
17+
%{
18+
_id: "foo",
19+
bar: "baz"
20+
}
21+
)
22+
23+
{:ok, _} = create_doc(
24+
db_name,
25+
%{
26+
_id: "foo2",
27+
bar: "baz2"
28+
}
29+
)
30+
31+
map_fun = """
32+
function(doc) {
33+
emit(doc._id, doc.bar);
34+
}
35+
"""
36+
37+
38+
body = %{
39+
:docs => [
40+
%{
41+
_id: "_design/map",
42+
views: %{
43+
some: %{
44+
map: map_fun
45+
}
46+
}
47+
}
48+
]
49+
}
50+
51+
resp = Couch.post("/#{db_name}/_bulk_docs", body: body)
52+
Enum.each(resp.body, &assert(&1["ok"]))
53+
54+
{:ok, [db_name: db_name]}
55+
end
56+
57+
test "GET with no parameters", context do
58+
resp = Couch.get(
59+
"/#{context[:db_name]}/_design/map/_view/some"
60+
)
61+
62+
assert resp.status_code == 200
63+
assert length(Map.get(resp, :body)["rows"]) == 2
64+
end
65+
66+
test "GET with one key", context do
67+
resp = Couch.get(
68+
"/#{context[:db_name]}/_design/map/_view/some",
69+
query: %{
70+
:key => "\"foo\"",
71+
}
72+
)
73+
74+
assert resp.status_code == 200
75+
assert length(Map.get(resp, :body)["rows"]) == 1
76+
end
77+
78+
test "GET with multiple keys", context do
79+
resp = Couch.get(
80+
"/#{context[:db_name]}/_design/map/_view/some",
81+
query: %{
82+
:keys => "[\"foo\", \"foo2\"]",
83+
}
84+
)
85+
86+
assert resp.status_code == 200
87+
assert length(Map.get(resp, :body)["rows"]) == 2
88+
end
89+
90+
test "POST with empty body", context do
91+
resp = Couch.post(
92+
"/#{context[:db_name]}/_design/map/_view/some",
93+
body: %{}
94+
)
95+
96+
assert resp.status_code == 200
97+
assert length(Map.get(resp, :body)["rows"]) == 2
98+
end
99+
100+
test "POST with keys and limit", context do
101+
resp = Couch.post(
102+
"/#{context[:db_name]}/_design/map/_view/some",
103+
body: %{
104+
:keys => ["foo", "foo2"],
105+
:limit => 1
106+
}
107+
)
108+
109+
assert resp.status_code == 200
110+
assert length(Map.get(resp, :body)["rows"]) == 1
111+
end
112+
113+
test "POST with query parameter and JSON body", context do
114+
resp = Couch.post(
115+
"/#{context[:db_name]}/_design/map/_view/some",
116+
query: %{
117+
:limit => 1
118+
},
119+
body: %{
120+
:keys => ["foo", "foo2"]
121+
}
122+
)
123+
124+
assert resp.status_code == 200
125+
assert length(Map.get(resp, :body)["rows"]) == 1
126+
end
127+
128+
test "POST edge case with colliding parameters - query takes precedence", context do
129+
resp = Couch.post(
130+
"/#{context[:db_name]}/_design/map/_view/some",
131+
query: %{
132+
:limit => 1
133+
},
134+
body: %{
135+
:keys => ["foo", "foo2"],
136+
:limit => 2
137+
}
138+
)
139+
140+
assert resp.status_code == 200
141+
assert length(Map.get(resp, :body)["rows"]) == 1
142+
end
143+
end

0 commit comments

Comments
 (0)