Skip to content

Commit 7ae4a2f

Browse files
committed
Allow all params to be passed via body for POST _all_docs
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. As /{db}/_design_docs and /{db}/_local_docs are analogous to _all_docs, this change applies to all three of them.
1 parent 4815eeb commit 7ae4a2f

File tree

7 files changed

+342
-4
lines changed

7 files changed

+342
-4
lines changed

src/chttpd/src/chttpd_db.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ multi_all_docs_view(Req, Db, OP, Queries) ->
882882
chttpd:end_delayed_json_response(Resp1).
883883

884884
all_docs_view(Req, Db, Keys, OP) ->
885-
Args0 = couch_mrview_http:parse_params(Req, Keys),
885+
Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
886886
Args1 = Args0#mrargs{view_type=map},
887887
Args2 = fabric_util:validate_all_docs_args(Db, Args1),
888888
Args3 = set_namespace(OP, Args2),

src/chttpd/src/chttpd_show.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
204204
DbName = couch_db:name(Db),
205205
{ok, VDoc} = ddoc_cache:open(DbName, <<"_design/", ViewDesignName/binary>>),
206206
CB = fun list_cb/2,
207-
QueryArgs = couch_mrview_http:parse_params(Req, Keys),
207+
QueryArgs = couch_mrview_http:parse_body_and_query(Req, Keys),
208208
Options = [{user_ctx, Req#httpd.user_ctx}],
209209
couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
210210
Acc = #lacc{

src/couch_mrview/src/couch_mrview_http.erl

Lines changed: 11 additions & 1 deletion
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/2,
3233
parse_body_and_query/3,
3334
parse_params/2,
3435
parse_params/3,
@@ -209,7 +210,7 @@ is_public_fields_configured(Db) ->
209210
end.
210211

211212
do_all_docs_req(Req, Db, Keys, NS) ->
212-
Args0 = parse_params(Req, Keys),
213+
Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
213214
Args1 = set_namespace(NS, Args0),
214215
ETagFun = fun(Sig, Acc0) ->
215216
check_view_etag(Sig, Acc0, Req)
@@ -465,6 +466,15 @@ parse_params(Props, Keys, #mrargs{}=Args0, Options) ->
465466
parse_param(K, V, Acc, IsDecoded)
466467
end, Args1, Props).
467468

469+
470+
parse_body_and_query(#httpd{method='POST'} = Req, Keys) ->
471+
Props = chttpd:json_body_obj(Req),
472+
parse_body_and_query(Req, Props, Keys);
473+
474+
parse_body_and_query(Req, Keys) ->
475+
parse_params(chttpd:qs(Req), Keys, #mrargs{keys=Keys, group=undefined,
476+
group_level=undefined}, [keep_group_level]).
477+
468478
parse_body_and_query(Req, {Props}, Keys) ->
469479
Args = #mrargs{keys=Keys, group=undefined, group_level=undefined},
470480
BodyArgs = parse_params(Props, Keys, Args, [decoded]),

src/couch_mrview/src/couch_mrview_show.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ handle_view_list_req(Req, _Db, _DDoc) ->
181181

182182

183183
handle_view_list(Req, Db, DDoc, LName, VDDoc, VName, Keys) ->
184-
Args0 = couch_mrview_http:parse_params(Req, Keys),
184+
Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
185185
ETagFun = fun(BaseSig, Acc0) ->
186186
UserCtx = Req#httpd.user_ctx,
187187
Name = UserCtx#user_ctx.name,

test/elixir/test/all_docs_test.exs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,114 @@ defmodule AllDocsTest do
186186

187187
assert length(rows) == 1
188188
end
189+
190+
@tag :with_db
191+
test "GET with one key", context do
192+
db_name = context[:db_name]
193+
194+
{:ok, _} = create_doc(
195+
db_name,
196+
%{
197+
_id: "foo",
198+
bar: "baz"
199+
}
200+
)
201+
202+
{:ok, _} = create_doc(
203+
db_name,
204+
%{
205+
_id: "foo2",
206+
bar: "baz2"
207+
}
208+
)
209+
210+
resp = Couch.get(
211+
"/#{db_name}/_all_docs",
212+
query: %{
213+
:key => "\"foo\"",
214+
}
215+
)
216+
217+
assert resp.status_code == 200
218+
assert length(Map.get(resp, :body)["rows"]) == 1
219+
end
220+
221+
222+
@tag :with_db
223+
test "POST with empty body", context do
224+
db_name = context[:db_name]
225+
226+
resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..2)})
227+
assert resp.status_code == 201
228+
229+
resp = Couch.post(
230+
"/#{db_name}/_all_docs",
231+
body: %{}
232+
)
233+
234+
assert resp.status_code == 200
235+
assert length(Map.get(resp, :body)["rows"]) == 3
236+
end
237+
238+
@tag :with_db
239+
test "POST with keys and limit", context do
240+
db_name = context[:db_name]
241+
242+
resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
243+
assert resp.status_code == 201
244+
245+
resp = Couch.post(
246+
"/#{db_name}/_all_docs",
247+
body: %{
248+
:keys => [1, 2],
249+
:limit => 1
250+
}
251+
)
252+
253+
assert resp.status_code == 200
254+
assert length(Map.get(resp, :body)["rows"]) == 1
255+
end
256+
257+
@tag :with_db
258+
test "POST with query parameter and JSON body", context do
259+
db_name = context[:db_name]
260+
261+
resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
262+
assert resp.status_code == 201
263+
264+
resp = Couch.post(
265+
"/#{db_name}/_all_docs",
266+
query: %{
267+
:limit => 1
268+
},
269+
body: %{
270+
:keys => [1, 2]
271+
}
272+
)
273+
274+
assert resp.status_code == 200
275+
assert length(Map.get(resp, :body)["rows"]) == 1
276+
end
277+
278+
@tag :with_db
279+
test "POST edge case with colliding parameters - query takes precedence", context do
280+
db_name = context[:db_name]
281+
282+
resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
283+
assert resp.status_code == 201
284+
285+
resp = Couch.post(
286+
"/#{db_name}/_all_docs",
287+
query: %{
288+
:limit => 1
289+
},
290+
body: %{
291+
:keys => [1, 2],
292+
:limit => 2
293+
}
294+
)
295+
296+
assert resp.status_code == 200
297+
assert length(Map.get(resp, :body)["rows"]) == 1
298+
end
189299
end
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
defmodule DesignDocsTest do
2+
use CouchTestCase
3+
4+
@moduletag :design_docs
5+
6+
@moduledoc """
7+
Test CouchDB /{db}/_design_docs
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: "_design/foo",
19+
bar: "baz"
20+
}
21+
)
22+
23+
{:ok, _} = create_doc(
24+
db_name,
25+
%{
26+
_id: "_design/foo2",
27+
bar: "baz2"
28+
}
29+
)
30+
31+
{:ok, [db_name: db_name]}
32+
end
33+
34+
test "GET with no parameters", context do
35+
resp = Couch.get(
36+
"/#{context[:db_name]}/_design_docs"
37+
)
38+
39+
assert resp.status_code == 200
40+
assert length(Map.get(resp, :body)["rows"]) == 2
41+
end
42+
43+
test "GET with multiple keys", context do
44+
resp = Couch.get(
45+
"/#{context[:db_name]}/_design_docs",
46+
query: %{
47+
:keys => "[\"_design/foo\", \"_design/foo2\"]",
48+
}
49+
)
50+
51+
assert resp.status_code == 200
52+
assert length(Map.get(resp, :body)["rows"]) == 2
53+
end
54+
55+
test "POST with empty body", context do
56+
resp = Couch.post(
57+
"/#{context[:db_name]}/_design_docs",
58+
body: %{}
59+
)
60+
61+
assert resp.status_code == 200
62+
assert length(Map.get(resp, :body)["rows"]) == 2
63+
end
64+
65+
test "POST with keys and limit", context do
66+
resp = Couch.post(
67+
"/#{context[:db_name]}/_design_docs",
68+
body: %{
69+
:keys => ["_design/foo", "_design/foo2"],
70+
:limit => 1
71+
}
72+
)
73+
74+
assert resp.status_code == 200
75+
assert length(Map.get(resp, :body)["rows"]) == 1
76+
end
77+
78+
test "POST with query parameter and JSON body", context do
79+
resp = Couch.post(
80+
"/#{context[:db_name]}/_design_docs",
81+
query: %{
82+
:limit => 1
83+
},
84+
body: %{
85+
:keys => ["_design/foo", "_design/foo2"]
86+
}
87+
)
88+
89+
assert resp.status_code == 200
90+
assert length(Map.get(resp, :body)["rows"]) == 1
91+
end
92+
93+
test "POST edge case with colliding parameters - query takes precedence", context do
94+
resp = Couch.post(
95+
"/#{context[:db_name]}/_design_docs",
96+
query: %{
97+
:limit => 0
98+
},
99+
body: %{
100+
:keys => ["_design/foo", "_design/foo2"],
101+
:limit => 2
102+
}
103+
)
104+
105+
assert resp.status_code == 200
106+
assert Enum.empty?(Map.get(resp, :body)["rows"])
107+
end
108+
end
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
defmodule LocalDocsTest do
2+
use CouchTestCase
3+
4+
@moduletag :local_docs
5+
6+
@moduledoc """
7+
Test CouchDB _local_docs
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+
resp1 = Couch.put(
16+
"/#{db_name}/_local/foo",
17+
body: %{
18+
_id: "foo",
19+
bar: "baz"
20+
}
21+
)
22+
assert resp1.status_code == 201
23+
24+
resp2 = Couch.put(
25+
"/#{db_name}/_local/foo2",
26+
body: %{
27+
_id: "foo",
28+
bar: "baz2"
29+
}
30+
)
31+
assert resp2.status_code == 201
32+
33+
{:ok, [db_name: db_name]}
34+
end
35+
36+
test "GET with no parameters", context do
37+
resp = Couch.get(
38+
"/#{context[:db_name]}/_local_docs"
39+
)
40+
41+
assert resp.status_code == 200
42+
assert length(Map.get(resp, :body)["rows"]) == 2
43+
end
44+
45+
test "GET with multiple keys", context do
46+
resp = Couch.get(
47+
"/#{context[:db_name]}/_local_docs",
48+
query: %{
49+
:keys => "[\"_local/foo\", \"_local/foo2\"]",
50+
}
51+
)
52+
53+
assert resp.status_code == 200
54+
assert length(Map.get(resp, :body)["rows"]) == 2
55+
end
56+
57+
test "POST with empty body", context do
58+
resp = Couch.post(
59+
"/#{context[:db_name]}/_local_docs",
60+
body: %{}
61+
)
62+
63+
assert resp.status_code == 200
64+
assert length(Map.get(resp, :body)["rows"]) == 2
65+
end
66+
67+
test "POST with keys and limit", context do
68+
resp = Couch.post(
69+
"/#{context[:db_name]}/_local_docs",
70+
body: %{
71+
:keys => ["_local/foo", "_local/foo2"],
72+
:limit => 1
73+
}
74+
)
75+
76+
assert resp.status_code == 200
77+
assert length(Map.get(resp, :body)["rows"]) == 1
78+
end
79+
80+
test "POST with query parameter and JSON body", context do
81+
resp = Couch.post(
82+
"/#{context[:db_name]}/_local_docs",
83+
query: %{
84+
:limit => 1
85+
},
86+
body: %{
87+
:keys => ["_local/foo", "_local/foo2"]
88+
}
89+
)
90+
91+
assert resp.status_code == 200
92+
assert length(Map.get(resp, :body)["rows"]) == 1
93+
end
94+
95+
test "POST edge case with colliding parameters - query takes precedence", context do
96+
resp = Couch.post(
97+
"/#{context[:db_name]}/_local_docs",
98+
query: %{
99+
:limit => 0
100+
},
101+
body: %{
102+
:keys => ["_local/foo", "_local/foo2"],
103+
:limit => 2
104+
}
105+
)
106+
107+
assert resp.status_code == 200
108+
assert Enum.empty?(Map.get(resp, :body)["rows"])
109+
end
110+
end

0 commit comments

Comments
 (0)