summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBessenyei Balázs Donát <bessbd@apache.org>2019-12-05 10:29:59 +0100
committergarren smith <garren.smith@gmail.com>2019-12-09 15:07:39 +0200
commit8ac108adcf7dfa2e9223de1568b71780938b57de (patch)
tree751c3ec0a7c630e6401135df26fedf59fec54fdc
parent4815eebe56f175db57f10dabf52fd1bce616cd29 (diff)
downloadcouchdb-8ac108adcf7dfa2e9223de1568b71780938b57de.tar.gz
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.
-rw-r--r--src/chttpd/src/chttpd_db.erl2
-rw-r--r--src/chttpd/src/chttpd_show.erl2
-rw-r--r--src/couch_mrview/src/couch_mrview_http.erl12
-rw-r--r--src/couch_mrview/src/couch_mrview_show.erl2
-rw-r--r--test/elixir/test/all_docs_test.exs110
-rw-r--r--test/elixir/test/design_docs_test.exs108
-rw-r--r--test/elixir/test/local_docs_test.exs110
7 files changed, 342 insertions, 4 deletions
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
index 89351cc6e..1787e3929 100644
--- a/src/chttpd/src/chttpd_db.erl
+++ b/src/chttpd/src/chttpd_db.erl
@@ -882,7 +882,7 @@ multi_all_docs_view(Req, Db, OP, Queries) ->
chttpd:end_delayed_json_response(Resp1).
all_docs_view(Req, Db, Keys, OP) ->
- Args0 = couch_mrview_http:parse_params(Req, Keys),
+ Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
Args1 = Args0#mrargs{view_type=map},
Args2 = fabric_util:validate_all_docs_args(Db, Args1),
Args3 = set_namespace(OP, Args2),
diff --git a/src/chttpd/src/chttpd_show.erl b/src/chttpd/src/chttpd_show.erl
index c3bf11929..a6d0368b9 100644
--- a/src/chttpd/src/chttpd_show.erl
+++ b/src/chttpd/src/chttpd_show.erl
@@ -204,7 +204,7 @@ handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
DbName = couch_db:name(Db),
{ok, VDoc} = ddoc_cache:open(DbName, <<"_design/", ViewDesignName/binary>>),
CB = fun list_cb/2,
- QueryArgs = couch_mrview_http:parse_params(Req, Keys),
+ QueryArgs = couch_mrview_http:parse_body_and_query(Req, Keys),
Options = [{user_ctx, Req#httpd.user_ctx}],
couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
Acc = #lacc{
diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl
index 69cbb7351..3cf8833d7 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/2,
parse_body_and_query/3,
parse_params/2,
parse_params/3,
@@ -209,7 +210,7 @@ is_public_fields_configured(Db) ->
end.
do_all_docs_req(Req, Db, Keys, NS) ->
- Args0 = parse_params(Req, Keys),
+ Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
Args1 = set_namespace(NS, Args0),
ETagFun = fun(Sig, Acc0) ->
check_view_etag(Sig, Acc0, Req)
@@ -465,6 +466,15 @@ parse_params(Props, Keys, #mrargs{}=Args0, Options) ->
parse_param(K, V, Acc, IsDecoded)
end, Args1, Props).
+
+parse_body_and_query(#httpd{method='POST'} = Req, Keys) ->
+ Props = chttpd:json_body_obj(Req),
+ parse_body_and_query(Req, Props, Keys);
+
+parse_body_and_query(Req, Keys) ->
+ parse_params(chttpd:qs(Req), Keys, #mrargs{keys=Keys, group=undefined,
+ group_level=undefined}, [keep_group_level]).
+
parse_body_and_query(Req, {Props}, Keys) ->
Args = #mrargs{keys=Keys, group=undefined, group_level=undefined},
BodyArgs = parse_params(Props, Keys, Args, [decoded]),
diff --git a/src/couch_mrview/src/couch_mrview_show.erl b/src/couch_mrview/src/couch_mrview_show.erl
index c9be5b063..9056907fa 100644
--- a/src/couch_mrview/src/couch_mrview_show.erl
+++ b/src/couch_mrview/src/couch_mrview_show.erl
@@ -181,7 +181,7 @@ handle_view_list_req(Req, _Db, _DDoc) ->
handle_view_list(Req, Db, DDoc, LName, VDDoc, VName, Keys) ->
- Args0 = couch_mrview_http:parse_params(Req, Keys),
+ Args0 = couch_mrview_http:parse_body_and_query(Req, Keys),
ETagFun = fun(BaseSig, Acc0) ->
UserCtx = Req#httpd.user_ctx,
Name = UserCtx#user_ctx.name,
diff --git a/test/elixir/test/all_docs_test.exs b/test/elixir/test/all_docs_test.exs
index 9f6aeb61d..5b40965f6 100644
--- a/test/elixir/test/all_docs_test.exs
+++ b/test/elixir/test/all_docs_test.exs
@@ -186,4 +186,114 @@ defmodule AllDocsTest do
assert length(rows) == 1
end
+
+ @tag :with_db
+ test "GET with one key", context do
+ db_name = context[:db_name]
+
+ {:ok, _} = create_doc(
+ db_name,
+ %{
+ _id: "foo",
+ bar: "baz"
+ }
+ )
+
+ {:ok, _} = create_doc(
+ db_name,
+ %{
+ _id: "foo2",
+ bar: "baz2"
+ }
+ )
+
+ resp = Couch.get(
+ "/#{db_name}/_all_docs",
+ query: %{
+ :key => "\"foo\"",
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+
+ @tag :with_db
+ test "POST with empty body", context do
+ db_name = context[:db_name]
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..2)})
+ assert resp.status_code == 201
+
+ resp = Couch.post(
+ "/#{db_name}/_all_docs",
+ body: %{}
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 3
+ end
+
+ @tag :with_db
+ test "POST with keys and limit", context do
+ db_name = context[:db_name]
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
+ assert resp.status_code == 201
+
+ resp = Couch.post(
+ "/#{db_name}/_all_docs",
+ body: %{
+ :keys => [1, 2],
+ :limit => 1
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+ @tag :with_db
+ test "POST with query parameter and JSON body", context do
+ db_name = context[:db_name]
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
+ assert resp.status_code == 201
+
+ resp = Couch.post(
+ "/#{db_name}/_all_docs",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => [1, 2]
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
+
+ @tag :with_db
+ test "POST edge case with colliding parameters - query takes precedence", context do
+ db_name = context[:db_name]
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)})
+ assert resp.status_code == 201
+
+ resp = Couch.post(
+ "/#{db_name}/_all_docs",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => [1, 2],
+ :limit => 2
+ }
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 1
+ end
end
diff --git a/test/elixir/test/design_docs_test.exs b/test/elixir/test/design_docs_test.exs
new file mode 100644
index 000000000..ed0a0dfb5
--- /dev/null
+++ b/test/elixir/test/design_docs_test.exs
@@ -0,0 +1,108 @@
+defmodule DesignDocsTest do
+ use CouchTestCase
+
+ @moduletag :design_docs
+
+ @moduledoc """
+ Test CouchDB /{db}/_design_docs
+ """
+
+ 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: "_design/foo",
+ bar: "baz"
+ }
+ )
+
+ {:ok, _} = create_doc(
+ db_name,
+ %{
+ _id: "_design/foo2",
+ bar: "baz2"
+ }
+ )
+
+ {:ok, [db_name: db_name]}
+ end
+
+ test "GET with no parameters", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_design_docs"
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 2
+ end
+
+ test "GET with multiple keys", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_design_docs",
+ query: %{
+ :keys => "[\"_design/foo\", \"_design/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_docs",
+ 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_docs",
+ body: %{
+ :keys => ["_design/foo", "_design/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_docs",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => ["_design/foo", "_design/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_docs",
+ query: %{
+ :limit => 0
+ },
+ body: %{
+ :keys => ["_design/foo", "_design/foo2"],
+ :limit => 2
+ }
+ )
+
+ assert resp.status_code == 200
+ assert Enum.empty?(Map.get(resp, :body)["rows"])
+ end
+end
diff --git a/test/elixir/test/local_docs_test.exs b/test/elixir/test/local_docs_test.exs
new file mode 100644
index 000000000..ff071f3e6
--- /dev/null
+++ b/test/elixir/test/local_docs_test.exs
@@ -0,0 +1,110 @@
+defmodule LocalDocsTest do
+ use CouchTestCase
+
+ @moduletag :local_docs
+
+ @moduledoc """
+ Test CouchDB _local_docs
+ """
+
+ setup_all do
+ db_name = random_db_name()
+ {:ok, _} = create_db(db_name)
+ on_exit(fn -> delete_db(db_name) end)
+
+ resp1 = Couch.put(
+ "/#{db_name}/_local/foo",
+ body: %{
+ _id: "foo",
+ bar: "baz"
+ }
+ )
+ assert resp1.status_code == 201
+
+ resp2 = Couch.put(
+ "/#{db_name}/_local/foo2",
+ body: %{
+ _id: "foo",
+ bar: "baz2"
+ }
+ )
+ assert resp2.status_code == 201
+
+ {:ok, [db_name: db_name]}
+ end
+
+ test "GET with no parameters", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_local_docs"
+ )
+
+ assert resp.status_code == 200
+ assert length(Map.get(resp, :body)["rows"]) == 2
+ end
+
+ test "GET with multiple keys", context do
+ resp = Couch.get(
+ "/#{context[:db_name]}/_local_docs",
+ query: %{
+ :keys => "[\"_local/foo\", \"_local/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]}/_local_docs",
+ 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]}/_local_docs",
+ body: %{
+ :keys => ["_local/foo", "_local/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]}/_local_docs",
+ query: %{
+ :limit => 1
+ },
+ body: %{
+ :keys => ["_local/foo", "_local/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]}/_local_docs",
+ query: %{
+ :limit => 0
+ },
+ body: %{
+ :keys => ["_local/foo", "_local/foo2"],
+ :limit => 2
+ }
+ )
+
+ assert resp.status_code == 200
+ assert Enum.empty?(Map.get(resp, :body)["rows"])
+ end
+end