diff options
author | Bessenyei Balázs Donát <bessbd@apache.org> | 2019-12-05 10:29:59 +0100 |
---|---|---|
committer | garren smith <garren.smith@gmail.com> | 2019-12-09 15:07:39 +0200 |
commit | 8ac108adcf7dfa2e9223de1568b71780938b57de (patch) | |
tree | 751c3ec0a7c630e6401135df26fedf59fec54fdc | |
parent | 4815eebe56f175db57f10dabf52fd1bce616cd29 (diff) | |
download | couchdb-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.erl | 2 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_show.erl | 2 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_http.erl | 12 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_show.erl | 2 | ||||
-rw-r--r-- | test/elixir/test/all_docs_test.exs | 110 | ||||
-rw-r--r-- | test/elixir/test/design_docs_test.exs | 108 | ||||
-rw-r--r-- | test/elixir/test/local_docs_test.exs | 110 |
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 |