diff options
author | Gabor Pali <gabor.pali@ibm.com> | 2023-05-16 14:21:33 +0200 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2023-05-16 12:28:37 -0400 |
commit | b195f3734e8bae24728b98a57ae863a9a6bbb796 (patch) | |
tree | ecb27338f60a2f74212d521128a72b595cfcc569 | |
parent | dcf57c710c52b0a55c596f65f07b85855130f66f (diff) | |
download | couchdb-b195f3734e8bae24728b98a57ae863a9a6bbb796.tar.gz |
mango: address missing parts of the `_index` API
Many of the requests aimed outside the scope of the `_index`
endpoint are not handled gracefully but trigger an internal server
error. Enhance the index HTTP REST API handler logic to return
proper answers for invalid queries and supply it with more
exhaustive integration tests.
Provide documentation for the existing `_index/_bulk_delete`
endpoint as it was missing, and mention that the `_design` prefix
is not needed when deleting indexes.
-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") |