summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabor Pali <gabor.pali@ibm.com>2023-05-16 14:21:33 +0200
committerNick Vatamaniuc <nickva@users.noreply.github.com>2023-05-16 12:28:37 -0400
commitb195f3734e8bae24728b98a57ae863a9a6bbb796 (patch)
treeecb27338f60a2f74212d521128a72b595cfcc569
parentdcf57c710c52b0a55c596f65f07b85855130f66f (diff)
downloadcouchdb-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.rst77
-rw-r--r--src/mango/src/mango_httpd.erl44
-rw-r--r--src/mango/test/01-index-crud-test.py51
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")