diff options
-rw-r--r-- | src/docs/src/api/database/find.rst | 77 | ||||
-rw-r--r-- | src/mango/src/mango_httpd.erl | 44 | ||||
-rw-r--r-- | src/mango/test/01-index-crud-test.py | 51 |
3 files changed, 151 insertions, 21 deletions
diff --git a/src/docs/src/api/database/find.rst b/src/docs/src/api/database/find.rst index ede5598c9..aed695543 100644 --- a/src/docs/src/api/database/find.rst +++ b/src/docs/src/api/database/find.rst @@ -1261,7 +1261,8 @@ it easier to take advantage of future improvements to query planning :synopsis: Delete an index. :param db: Database name. - :param design_doc: Design document name. + :param design_doc: Design document name. The ``_design/`` prefix + is not required. :param name: Index name. :>header Content-Type: - :mimetype:`application/json` @@ -1297,6 +1298,80 @@ it easier to take advantage of future improvements to query planning "ok": true } +.. _api/db/find/index-bulk-delete: + +.. http:post:: /{db}/_index/_bulk_delete + :synopsis: Delete indexes in bulk. + + :param db: Database name + + :<header Content-Type: - :mimetype:`application/json` + + :<json array docids: List of names for indexes to be deleted. + :<json number w: Write quorum for each of the deletions. Default + is ``2``. *Optional* + + :>header Content-Type: - :mimetype:`application/json` + + :>json array success: An array of objects that represent successful + deletions per index. The ``id`` key contains the name of the + index, and ``ok`` reports if the operation has completed + :>json array fail: An array of object that describe failed + deletions per index. The ``id`` key names the corresponding + index, and ``error`` describes the reason for the failure + + :code 200: Success + :code 400: Invalid request + :code 404: Requested database not found + :code 500: Execution error + + **Request**: + + .. code-block:: http + + POST /db/_index/_bulk_delete HTTP/1.1 + Accept: application/json + Content-Type: application/json + Host: localhost:5984 + + { + "docids": [ + "_design/example-ddoc", + "foo-index", + "nonexistent-index" + ] + } + + **Response**: + + .. code-block:: http + + HTTP/1.1 200 OK + Cache-Control: must-revalidate + Content-Length: 94 + Content-Type: application/json + Date: Thu, 01 Sep 2016 19:26:59 GMT + Server: CouchDB (Erlang OTP/18) + + { + "success": [ + { + "id": "_design/example-ddoc", + "ok": true + }, + { + "id": "foo-index", + "ok": true + } + ], + "fail": [ + { + "id": "nonexistent-index", + "error": "not_found" + } + ] + } + .. _api/db/find/explain: ================== diff --git a/src/mango/src/mango_httpd.erl b/src/mango/src/mango_httpd.erl index 3e58288da..ed797c517 100644 --- a/src/mango/src/mango_httpd.erl +++ b/src/mango/src/mango_httpd.erl @@ -157,7 +157,6 @@ handle_index_req( chttpd:send_method_not_allowed(Req, "POST"); handle_index_req( #httpd{ - method = 'DELETE', path_parts = [A, B, <<"_design">>, DDocId0, Type, Name] } = Req, Db @@ -166,30 +165,35 @@ handle_index_req( handle_index_req(Req#httpd{path_parts = PathParts}, Db); handle_index_req( #httpd{ - method = 'DELETE', + method = Method, path_parts = [_, _, DDocId0, Type, Name] } = Req, Db ) -> - Idxs = mango_idx:list(Db), - DDocId = convert_to_design_id(DDocId0), - DelOpts = get_idx_del_opts(Req), - Filt = fun(Idx) -> - IsDDoc = mango_idx:ddoc(Idx) == DDocId, - IsType = mango_idx:type(Idx) == Type, - IsName = mango_idx:name(Idx) == Name, - IsDDoc andalso IsType andalso IsName - end, - case mango_idx:delete(Filt, Db, Idxs, DelOpts) of - {ok, true} -> - chttpd:send_json(Req, {[{ok, true}]}); - {error, not_found} -> - throw({not_found, missing}); - {error, Error} -> - ?MANGO_ERROR({error_saving_ddoc, Error}) + case Method of + 'DELETE' -> + Idxs = mango_idx:list(Db), + DDocId = convert_to_design_id(DDocId0), + DelOpts = get_idx_del_opts(Req), + Filt = fun(Idx) -> + IsDDoc = mango_idx:ddoc(Idx) == DDocId, + IsType = mango_idx:type(Idx) == Type, + IsName = mango_idx:name(Idx) == Name, + IsDDoc andalso IsType andalso IsName + end, + case mango_idx:delete(Filt, Db, Idxs, DelOpts) of + {ok, true} -> + chttpd:send_json(Req, {[{ok, true}]}); + {error, not_found} -> + throw({not_found, missing}); + {error, Error} -> + ?MANGO_ERROR({error_saving_ddoc, Error}) + end; + _ -> + chttpd:send_method_not_allowed(Req, "DELETE") end; -handle_index_req(#httpd{path_parts = [_, _, _DDocId0, _Type, _Name]} = Req, _Db) -> - chttpd:send_method_not_allowed(Req, "DELETE"). +handle_index_req(Req, _Db) -> + chttpd:send_error(Req, not_found). handle_explain_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), diff --git a/src/mango/test/01-index-crud-test.py b/src/mango/test/01-index-crud-test.py index dd70e7eea..24409b49a 100644 --- a/src/mango/test/01-index-crud-test.py +++ b/src/mango/test/01-index-crud-test.py @@ -11,6 +11,7 @@ # the License. import random +from functools import partial import mango import copy @@ -88,6 +89,56 @@ class IndexCrudTests(mango.DbPerClass): else: raise AssertionError("bad create index") + def test_bad_urls(self): + # These are only the negative test cases because ideally the + # positive ones are implicitly tested by other ones. + + all_methods = [ + ("PUT", self.db.sess.put), + ("GET", self.db.sess.get), + ("POST", self.db.sess.post), + ("PATCH", self.db.sess.get), + ("DELETE", self.db.sess.delete), + ("HEAD", self.db.sess.head), + ("COPY", partial(self.db.sess.request, "COPY")), + ("OPTIONS", partial(self.db.sess.request, "OPTIONS")), + ("TRACE", partial(self.db.sess.request, "TRACE")), + ("CONNECT", partial(self.db.sess.request, "CONNECT")), + ] + + def all_but(method): + return list(filter(lambda x: x[0] != method, all_methods)) + + # Three-element subpaths are used as a shorthand to delete + # indexes via design documents, see below. + for subpath in ["a", "a/b", "a/b/c/d", "a/b/c/d/e", "a/b/c/d/e/f"]: + path = self.db.path("_index/{}".format(subpath)) + for method_name, method in all_methods: + with self.subTest(path=path, method=method_name): + r = method(path) + self.assertEqual(r.status_code, 404) + + for method_name, method in all_but("POST"): + path = self.db.path("_index/_bulk_delete") + with self.subTest(path=path, method=method_name): + r = method(path) + self.assertEqual(r.status_code, 405) + + fields = ["foo", "bar"] + ddoc = "dd" + idx = "idx_01" + ret = self.db.create_index(fields, name=idx, ddoc=ddoc) + assert ret is True + for subpath in [ + "{}/json/{}".format(ddoc, idx), + "_design/{}/json/{}".format(ddoc, idx), + ]: + path = self.db.path("_index/{}".format(subpath)) + for method_name, method in all_but("DELETE"): + r = method(path) + with self.subTest(path=path, method=method_name): + self.assertEqual(r.status_code, 405) + def test_create_idx_01(self): fields = ["foo", "bar"] ret = self.db.create_index(fields, name="idx_01") |