You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2023/05/16 16:28:44 UTC

[couchdb] branch main updated: mango: address missing parts of the `_index` API

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new b195f3734 mango: address missing parts of the `_index` API
b195f3734 is described below

commit b195f3734e8bae24728b98a57ae863a9a6bbb796
Author: Gabor Pali <ga...@ibm.com>
AuthorDate: Tue May 16 14:21:33 2023 +0200

    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.
---
 src/docs/src/api/database/find.rst   | 77 +++++++++++++++++++++++++++++++++++-
 src/mango/src/mango_httpd.erl        | 44 +++++++++++----------
 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")