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

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

commit dc27d4d92ce67d4f8083aafa5837620717e5e001
Author: jiahuili <[email protected]>
AuthorDate: Wed Jul 19 12:24:56 2023 -0500

    Fix: `_design_docs/queries` with `keys` should only return design docs
    
    Querying design/local docs with `keys` should only return design/local docs.
    
    Fixed: https://github.com/apache/couchdb/issues/2851
---
 src/chttpd/test/eunit/chttpd_db_test.erl | 939 +++++++++++--------------------
 src/fabric/src/fabric_view_all_docs.erl  |  44 +-
 2 files changed, 354 insertions(+), 629 deletions(-)

diff --git a/src/chttpd/test/eunit/chttpd_db_test.erl 
b/src/chttpd/test/eunit/chttpd_db_test.erl
index d6b980c07..6560cc949 100644
--- a/src/chttpd/test/eunit/chttpd_db_test.erl
+++ b/src/chttpd/test/eunit/chttpd_db_test.erl
@@ -29,31 +29,14 @@
 setup() ->
     Hashed = couch_passwords:hash_admin_password(?PASS),
     ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
-    TmpDb = ?tempdb(),
-    Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
-    Port = mochiweb_socket_server:get(chttpd, port),
-    Url = lists:concat(["http://";, Addr, ":", Port, "/", ?b2l(TmpDb)]),
-    create_db(Url),
-    Url.
+    Db = ?tempdb(),
+    create_db(Db),
+    Db.
 
-teardown(Url) ->
-    delete_db(Url),
+teardown(Db) ->
+    delete_db(Db),
     ok = config:delete("admins", ?USER, _Persist = false).
 
-create_db(Url) ->
-    {ok, Status, _, _} = test_request:put(Url, [?CONTENT_JSON, ?AUTH], "{}"),
-    ?assert(Status =:= 201 orelse Status =:= 202).
-
-create_doc(Url, Id) ->
-    test_request:put(
-        Url ++ "/" ++ Id,
-        [?CONTENT_JSON, ?AUTH],
-        "{\"mr\": \"rockoartischocko\"}"
-    ).
-
-delete_db(Url) ->
-    {ok, 200, _, _} = test_request:delete(Url, [?AUTH]).
-
 all_test_() ->
     {
         "chttpd db tests",
@@ -66,609 +49,327 @@ all_test_() ->
                 fun setup/0,
                 fun teardown/1,
                 [
-                    fun should_return_ok_true_on_bulk_update/1,
-                    fun 
should_return_201_new_edits_false_with_revs_on_bulk_update/1,
-                    fun 
should_return_400_new_edits_false_no_revs_on_bulk_update/1,
-                    fun 
should_return_201_new_edits_false_with_rev_on_doc_update/1,
-                    fun 
should_return_201_new_edits_false_with_revisions_on_doc_update/1,
-                    fun 
should_return_400_new_edits_false_no_revs_on_doc_update/1,
-                    fun should_return_ok_true_on_ensure_full_commit/1,
-                    fun should_return_404_for_ensure_full_commit_on_no_db/1,
-                    fun should_accept_live_as_an_alias_for_continuous/1,
-                    fun should_return_headers_after_starting_continious/1,
-                    fun should_return_404_for_delete_att_on_notadoc/1,
-                    fun should_return_409_for_del_att_without_rev/1,
-                    fun should_return_200_for_del_att_with_rev/1,
-                    fun should_return_409_for_put_att_nonexistent_rev/1,
-                    fun should_return_update_seq_when_set_on_all_docs/1,
-                    fun should_not_return_update_seq_when_unset_on_all_docs/1,
-                    fun should_return_correct_id_on_doc_copy/1,
-                    fun should_return_only_one_ok_on_doc_copy/1,
-                    fun should_return_400_for_bad_engine/1,
-                    fun should_not_change_db_proper_after_rewriting_shardmap/1,
-                    fun should_succeed_on_all_docs_with_queries_keys/1,
-                    fun should_succeed_on_all_docs_with_queries_limit_skip/1,
-                    fun should_succeed_on_all_docs_with_multiple_queries/1,
-                    fun should_succeed_on_design_docs_with_queries_keys/1,
-                    fun 
should_succeed_on_design_docs_with_queries_limit_skip/1,
-                    fun should_succeed_on_design_docs_with_multiple_queries/1,
-                    fun should_succeed_on_local_docs_with_queries_keys/1,
-                    fun should_succeed_on_local_docs_with_queries_limit_skip/1,
-                    fun should_succeed_on_local_docs_with_multiple_queries/1
+                    ?TDEF_FE(t_return_ok_true_on_bulk_update, ?TIMEOUT),
+                    
?TDEF_FE(t_return_201_new_edits_false_with_revs_on_bulk_update, ?TIMEOUT),
+                    
?TDEF_FE(t_return_400_new_edits_false_no_revs_on_bulk_update, ?TIMEOUT),
+                    
?TDEF_FE(t_return_201_new_edits_false_with_rev_on_doc_update, ?TIMEOUT),
+                    
?TDEF_FE(t_return_201_new_edits_false_with_revisions_on_doc_update, ?TIMEOUT),
+                    
?TDEF_FE(t_return_400_new_edits_false_no_revs_on_doc_update, ?TIMEOUT),
+                    ?TDEF_FE(t_return_ok_true_on_ensure_full_commit, ?TIMEOUT),
+                    ?TDEF_FE(t_return_404_for_ensure_full_commit_on_no_db, 
?TIMEOUT),
+                    ?TDEF_FE(t_accept_live_as_an_alias_for_continuous, 
?TIMEOUT),
+                    ?TDEF_FE(t_return_headers_after_starting_continuous, 
?TIMEOUT),
+                    ?TDEF_FE(t_return_404_for_delete_att_on_notadoc, ?TIMEOUT),
+                    ?TDEF_FE(t_return_409_for_del_att_without_rev, ?TIMEOUT),
+                    ?TDEF_FE(t_return_200_for_del_att_with_rev, ?TIMEOUT),
+                    ?TDEF_FE(t_return_409_for_put_att_nonexistent_rev, 
?TIMEOUT),
+                    ?TDEF_FE(t_return_update_seq_when_set_on_all_docs, 
?TIMEOUT),
+                    ?TDEF_FE(t_not_return_update_seq_when_unset_on_all_docs, 
?TIMEOUT),
+                    ?TDEF_FE(t_return_correct_id_on_doc_copy, ?TIMEOUT),
+                    ?TDEF_FE(t_return_only_one_ok_on_doc_copy, ?TIMEOUT),
+                    ?TDEF_FE(t_return_400_for_bad_engine, ?TIMEOUT),
+                    ?TDEF_FE(t_not_change_db_proper_after_rewriting_shardmap, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_all_docs_with_queries_keys, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_all_docs_with_queries_limit_skip, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_all_docs_with_multiple_queries, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_design_docs_with_queries_keys, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_design_docs_with_queries_limit_skip, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_design_docs_with_multiple_queries, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_local_docs_with_queries_keys, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_local_docs_with_queries_limit_skip, 
?TIMEOUT),
+                    ?TDEF_FE(t_succeed_on_local_docs_with_multiple_queries, 
?TIMEOUT)
                 ]
             }
         }
     }.
 
-should_return_ok_true_on_bulk_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_assertEqual(
-            true,
-            begin
-                {ok, _, _, Body} = create_doc(Url, "testdoc"),
-                {Json} = ?JSON_DECODE(Body),
-                Ref = couch_util:get_value(<<"rev">>, Json, undefined),
-                NewDoc = "{\"docs\": [{\"_rev\": \"" ++ ?b2l(Ref) ++ "\", 
\"_id\": \"testdoc\"}]}",
-                {ok, _, _, ResultBody} = test_request:post(
-                    Url ++ "/_bulk_docs/",
-                    [?CONTENT_JSON, ?AUTH],
-                    NewDoc
-                ),
-                ResultJson = ?JSON_DECODE(ResultBody),
-                {InnerJson} = lists:nth(1, ResultJson),
-                couch_util:get_value(<<"ok">>, InnerJson, undefined)
-            end
-        )}.
-
-should_return_201_new_edits_false_with_revs_on_bulk_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(
-            begin
-                {ok, _, _, Body} = create_doc(Url, "dochasrev"),
-                {Json} = ?JSON_DECODE(Body),
-                Ref = couch_util:get_value(<<"rev">>, Json, undefined),
-                NewDoc =
-                    "{\"docs\": [{\"_rev\": \"" ++ ?b2l(Ref) ++
-                        "\", \"_id\": \"dochasrev\"}], \"new_edits\": false}",
-                {ok, Status, _, ResultBody} = test_request:post(
-                    Url ++
-                        "/_bulk_docs/",
-                    [?CONTENT_JSON, ?AUTH],
-                    NewDoc
-                ),
-                ?assertEqual(201, Status),
-                ?assertEqual([], ?JSON_DECODE(ResultBody))
-            end
-        )}.
-
-should_return_400_new_edits_false_no_revs_on_bulk_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(
-            begin
-                {ok, _, _, _} = create_doc(Url, "docnorev"),
-                NewDoc =
-                    "{\"docs\": [{\"_id\": \"docnorev\"}], " ++
-                        "\"new_edits\": false}",
-                {ok, Status, _, ResultBody} = test_request:post(
-                    Url ++
-                        "/_bulk_docs/",
-                    [?CONTENT_JSON, ?AUTH],
-                    NewDoc
-                ),
-                {ResultJson} = ?JSON_DECODE(ResultBody),
-                ?assertEqual(400, Status),
-                ?assertEqual(
-                    <<"bad_request">>,
-                    couch_util:get_value(<<"error">>, ResultJson)
-                )
-            end
-        )}.
-
-should_return_201_new_edits_false_with_rev_on_doc_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(
-            begin
-                Url1 = Url ++ "/docrev2?new_edits=false",
-                Headers = [?CONTENT_JSON, ?AUTH],
-                Body = "{\"foo\": \"bar\", \"_rev\": \"1-abc\"}",
-                {ok, Status, _, ResultBody} = test_request:put(Url1, Headers, 
Body),
-                {Props} = ?JSON_DECODE(ResultBody),
-                ?assertEqual(201, Status),
-                Id = couch_util:get_value(<<"id">>, Props),
-                Rev = couch_util:get_value(<<"rev">>, Props),
-                ?assertEqual(<<"docrev2">>, Id),
-                ?assertEqual(<<"1-abc">>, Rev)
-            end
-        )}.
-
-should_return_201_new_edits_false_with_revisions_on_doc_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(
-            begin
-                Url1 = Url ++ "/docrev3?new_edits=false",
-                Headers = [?CONTENT_JSON, ?AUTH],
-                Body =
-                    "{\"foo\": \"bar\", " ++
-                        "\"_revisions\": {" ++
-                        "\"ids\": [\"abc\", \"def\"], " ++
-                        "\"start\": 2}} ",
-                {ok, Status, _, ResultBody} = test_request:put(Url1, Headers, 
Body),
-                {Props} = ?JSON_DECODE(ResultBody),
-                Id = couch_util:get_value(<<"id">>, Props),
-                Rev = couch_util:get_value(<<"rev">>, Props),
-                ?assertEqual(201, Status),
-                ?assertEqual(<<"docrev3">>, Id),
-                ?assertEqual(<<"2-abc">>, Rev)
-            end
-        )}.
-
-should_return_400_new_edits_false_no_revs_on_doc_update(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(
-            begin
-                Url1 = Url ++ "/docnorev4?new_edits=false",
-                Headers = [?CONTENT_JSON, ?AUTH],
-                Body = "{\"foo\": \"bar\"}",
-                {ok, Status, _, ResultBody} = test_request:put(Url1, Headers, 
Body),
-                {Props} = ?JSON_DECODE(ResultBody),
-                ?assertEqual(400, Status),
-                Error = couch_util:get_value(<<"error">>, Props),
-                ?assertEqual(<<"bad_request">>, Error)
+t_return_ok_true_on_bulk_update(Db) ->
+    {_, #{<<"rev">> := Rev}} = create_doc(Db, "testdoc"),
+    Body = #{<<"docs">> => [#{<<"_id">> => <<"testdoc">>, <<"_rev">> => Rev}]},
+    {_, [#{<<"ok">> := Res}]} = req(post, url(Db, "_bulk_docs"), Body),
+    ?assert(Res).
+
+t_return_201_new_edits_false_with_revs_on_bulk_update(Db) ->
+    {_, #{<<"rev">> := Rev}} = create_doc(Db, "dochasrev"),
+    Body = #{
+        <<"docs">> => [#{<<"_id">> => <<"dochasrev">>, <<"_rev">> => Rev}],
+        <<"new_edits">> => false
+    },
+    {Status, Response} = req(post, url(Db, "_bulk_docs"), Body),
+    ?assertEqual(201, Status),
+    ?assertEqual([], Response).
+
+t_return_400_new_edits_false_no_revs_on_bulk_update(Db) ->
+    create_doc(Db, "docnorev"),
+    NewDoc = #{<<"docs">> => [#{<<"_id">> => <<"docnorev">>}], <<"new_edits">> 
=> false},
+    {Status, Response} = req(post, url(Db, "_bulk_docs"), NewDoc),
+    ?assertEqual(400, Status),
+    ?assertMatch(#{<<"error">> := <<"bad_request">>}, Response).
+
+t_return_201_new_edits_false_with_rev_on_doc_update(Db) ->
+    Body = #{<<"foo">> => <<"bar">>, <<"_rev">> => <<"1-abc">>},
+    {Status, #{<<"id">> := Id, <<"rev">> := Rev}} =
+        req(put, url(Db, "docrev2?new_edits=false"), Body),
+    ?assertEqual(201, Status),
+    ?assertEqual(<<"docrev2">>, Id),
+    ?assertEqual(<<"1-abc">>, Rev).
+
+t_return_201_new_edits_false_with_revisions_on_doc_update(Db) ->
+    Body = #{
+        <<"foo">> => <<"bar">>,
+        <<"_revisions">> => #{<<"ids">> => [<<"abc">>, <<"def">>], <<"start">> 
=> 2}
+    },
+    {Status, #{<<"id">> := Id, <<"rev">> := Rev}} =
+        req(put, url(Db, "docrev3?new_edits=false"), Body),
+    ?assertEqual(201, Status),
+    ?assertEqual(<<"docrev3">>, Id),
+    ?assertEqual(<<"2-abc">>, Rev).
+
+t_return_400_new_edits_false_no_revs_on_doc_update(Db) ->
+    Body = #{<<"foo">> => <<"bar">>},
+    {Status, Response} = req(put, url(Db, "docrev3?new_edits=false"), Body),
+    ?assertEqual(400, Status),
+    ?assertMatch(#{<<"error">> := <<"bad_request">>}, Response).
+
+t_return_ok_true_on_ensure_full_commit(Db) ->
+    {Status, #{<<"ok">> := Res}} = req(post, url(Db, "_ensure_full_commit")),
+    ?assertEqual(201, Status),
+    ?assert(Res).
+
+t_return_404_for_ensure_full_commit_on_no_db(Db) ->
+    {Status, Response} = req(post, url(Db) ++ "-missing-db" ++ 
"/_ensure_full_commit"),
+    ?assertEqual(404, Status),
+    ?assertMatch(#{<<"error">> := <<"not_found">>}, Response).
+
+t_accept_live_as_an_alias_for_continuous(Db) ->
+    GetLastSeq =
+        fun(Chunks) ->
+            LastSeqBin = lists:last(Chunks),
+            {Result} =
+                try ?JSON_DECODE(LastSeqBin) of
+                    Data -> Data
+                catch
+                    _:_ ->
+                        % should not happen, abort
+                        ?assert(false)
+                end,
+            couch_util:get_value(<<"last_seq">>, Result, undefined)
+        end,
+    LastSeq1 = GetLastSeq(wait_non_empty_chunk(Db)),
+    create_doc(Db, "testdoc2"),
+    LastSeq2 = GetLastSeq(wait_non_empty_chunk(Db)),
+    ?assertNotEqual(LastSeq1, LastSeq2).
+
+t_return_headers_after_starting_continuous(Db) ->
+    {ok, _, _, Bin} = test_request:get(url(Db, 
"_changes?feed=live&timeout=1"), [?AUTH]),
+    Parts = binary:split(Bin, <<"\n">>, [global]),
+    %% we should receive at least one part even when timeout=1
+    ?assertNotEqual([], Parts).
+
+t_return_404_for_delete_att_on_notadoc(Db) ->
+    {Status, Response} = req(delete, url(Db, "notadoc/att.pdf")),
+    ?assertEqual(404, Status),
+    ?assertMatch(#{<<"error">> := <<"not_found">>, <<"reason">> := 
<<"missing">>}, Response),
+    {Status1, Response} = req(get, url(Db, "notadoc")),
+    ?assertEqual(404, Status1).
+
+t_return_409_for_del_att_without_rev(Db) ->
+    {Status, _} = req(put, url(Db, "testdoc3"), attachment_doc()),
+    ?assertEqual(201, Status),
+    {Status1, _} = req(delete, url(Db, "testdoc3/file.erl")),
+    ?assertEqual(409, Status1).
+
+t_return_200_for_del_att_with_rev(Db) ->
+    {Status, #{<<"rev">> := Rev}} = req(put, url(Db, "testdoc4"), 
attachment_doc()),
+    ?assertEqual(201, Status),
+    {Status1, _} = req(delete, url(Db, "testdoc4/file.erl?rev=" ++ Rev)),
+    ?assertEqual(200, Status1).
+
+t_return_409_for_put_att_nonexistent_rev(Db) ->
+    {Status, Response} = req(put, url(Db, "t_return_404/file.erl?rev=1-000"), 
attachment_doc()),
+    ?assertEqual(409, Status),
+    ?assertMatch(#{<<"error">> := <<"not_found">>, <<"reason">> := 
<<"missing_rev">>}, Response).
+
+t_return_update_seq_when_set_on_all_docs(Db) ->
+    [create_doc(Db, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)],
+    {Status, #{<<"update_seq">> := UpdateSeq, <<"offset">> := Offset}} =
+        req(get, url(Db, "_all_docs?update_seq=true&keys=[\"testdoc1\"]")),
+    ?assertEqual(200, Status),
+    ?assertNotEqual(undefined, UpdateSeq),
+    ?assertNotEqual(undefined, Offset).
+
+t_not_return_update_seq_when_unset_on_all_docs(Db) ->
+    [create_doc(Db, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)],
+    {Status, #{<<"offset">> := Offset} = Response} =
+        req(get, url(Db, "_all_docs?update_seq=false&keys=[\"testdoc1\"]")),
+    ?assertEqual(200, Status),
+    ?assertNot(maps:is_key(<<"update_seq">>, Response)),
+    ?assertNotEqual(undefined, Offset).
+
+t_return_correct_id_on_doc_copy(Db) ->
+    create_doc(Db, "testdoc"),
+    {_, #{<<"id">> := Id1}} = req(copy, url(Db, "testdoc"), [?CONTENT_JSON, 
?AUTH, ?DESTHEADER1]),
+    {_, #{<<"id">> := Id2}} = req(copy, url(Db, "testdoc"), [?CONTENT_JSON, 
?AUTH, ?DESTHEADER2]),
+    ?assertEqual(<<102, 111, 111, 229, 149, 138, 98, 97, 114>>, Id1),
+    ?assertEqual(<<"foo/bar#baz?pow:fiz">>, Id2).
+
+t_return_only_one_ok_on_doc_copy(Db) ->
+    create_doc(Db, "testdoc"),
+    {_, #{<<"ok">> := Res}} = req(copy, url(Db, "testdoc"), [?CONTENT_JSON, 
?AUTH, ?DESTHEADER1]),
+    ?assert(Res).
+
+t_return_400_for_bad_engine(Db) ->
+    {Status, _} = req(put, url(Db) ++ "?engine=cowabunga"),
+    ?assertEqual(400, Status).
+
+t_not_change_db_proper_after_rewriting_shardmap(Db) ->
+    delete_db(Db),
+    {201, _} = req(put, url(Db) ++ "?partitioned=true&q=1"),
+    ShardDbName = ?l2b(config:get("mem3", "shards_db", "_dbs")),
+    {ok, ShardDb} = mem3_util:ensure_exists(ShardDbName),
+    {ok, #doc{body = {Props}}} = couch_db:open_doc(
+        ShardDb, Db, [ejson_body]
+    ),
+    Shards = mem3_util:build_shards(Db, Props),
+    {Prop2} = ?JSON_DECODE(?JSON_ENCODE({Props})),
+    Shards2 = mem3_util:build_shards(Db, Prop2),
+    ?assertEqual(Shards2, Shards).
+
+t_succeed_on_all_docs_with_queries_keys(Db) ->
+    helper_queries_with_keys(Db, "_all_docs", 3).
+
+t_succeed_on_design_docs_with_queries_keys(Db) ->
+    helper_queries_with_keys(Db, "_design_docs", 1).
+
+t_succeed_on_local_docs_with_queries_keys(Db) ->
+    helper_queries_with_keys(Db, "_local_docs", 1).
+
+helper_queries_with_keys(Db, Path, ExpectedRows) ->
+    [create_doc(Db, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 5)],
+    [create_doc(Db, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 5)],
+    [create_doc(Db, "_local/doc" ++ ?i2l(I)) || I <- lists:seq(1, 5)],
+    QueryDoc = #{
+        <<"queries">> => [
+            #{
+                <<"keys">> =>
+                    [<<"testdoc1">>, <<"_design/ddoc3">>, <<"_local/doc5">>]
+            }
+        ]
+    },
+    {Status, #{<<"results">> := [#{<<"rows">> := Rows}]}} =
+        req(post, url(Db, Path ++ "/queries"), QueryDoc),
+    ?assertEqual(200, Status),
+    ?assertEqual(ExpectedRows, length(Rows)).
+
+t_succeed_on_all_docs_with_queries_limit_skip(Db) ->
+    helper_queries_with_limit_skip(Db, "testdoc", "_all_docs", 2, 5).
+
+t_succeed_on_design_docs_with_queries_limit_skip(Db) ->
+    helper_queries_with_limit_skip(Db, "_design/ddoc", "_design_docs", 2, 5).
+
+t_succeed_on_local_docs_with_queries_limit_skip(Db) ->
+    helper_queries_with_limit_skip(Db, "_local/doc", "_local_docs", null, 5).
+
+helper_queries_with_limit_skip(Db, Id, Path, ExpectedOffset, ExpectedRows) ->
+    [create_doc(Db, Id ++ ?i2l(I)) || I <- lists:seq(1, 10)],
+    QueryDoc = #{<<"queries">> => [#{<<"limit">> => 5, <<"skip">> => 2}]},
+    {Status, #{<<"results">> := [#{<<"offset">> := Offset, <<"rows">> := 
Rows}]}} =
+        req(post, url(Db, Path ++ "/queries"), QueryDoc),
+    ?assertEqual(200, Status),
+    ?assertEqual(ExpectedOffset, Offset),
+    ?assertEqual(ExpectedRows, length(Rows)).
+
+t_succeed_on_all_docs_with_multiple_queries(Db) ->
+    helper_queries_with_keys_limit_skip(Db, "_all_docs", 2, 3, 5).
+
+t_succeed_on_design_docs_with_multiple_queries(Db) ->
+    helper_queries_with_keys_limit_skip(Db, "_design_docs", 2, 1, 5).
+
+t_succeed_on_local_docs_with_multiple_queries(Db) ->
+    helper_queries_with_keys_limit_skip(Db, "_local_docs", null, 1, 5).
+
+helper_queries_with_keys_limit_skip(Db, Path, ExpectedOffset, ExpectedRows1, 
ExpectedRows2) ->
+    [create_doc(Db, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
+    [create_doc(Db, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
+    [create_doc(Db, "_local/doc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
+    QueryDoc = #{
+        <<"queries">> => [
+            #{
+                <<"keys">> =>
+                    [<<"testdoc1">>, <<"_design/ddoc3">>, <<"_local/doc5">>]
+            },
+            #{<<"limit">> => 5, <<"skip">> => 2}
+        ]
+    },
+    {Status, #{
+        <<"results">> := [
+            #{<<"rows">> := Rows1},
+            #{<<"offset">> := Offset2, <<"rows">> := Rows2}
+        ]
+    }} = req(post, url(Db, Path ++ "/queries"), QueryDoc),
+    ?assertEqual(200, Status),
+    ?assertEqual(ExpectedRows1, length(Rows1)),
+    ?assertEqual(ExpectedOffset, Offset2),
+    ?assertEqual(ExpectedRows2, length(Rows2)).
+
+%%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
+url(Db) ->
+    Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
+    Port = mochiweb_socket_server:get(chttpd, port),
+    lists:concat(["http://";, Addr, ":", Port, "/", ?b2l(Db)]).
+
+url(Db, Path) ->
+    url(Db) ++ "/" ++ Path.
+
+create_db(Db) ->
+    case req(put, url(Db)) of
+        {201, #{}} -> ok;
+        Error -> error({failed_to_create_test_db, Db, Error})
+    end.
+
+delete_db(Db) ->
+    case req(delete, url(Db)) of
+        {200, #{}} -> ok;
+        Error -> error({failed_to_delete_test_db, Db, Error})
+    end.
+
+create_doc(Db, Id) ->
+    req(put, url(Db, Id), #{<<"mr">> => <<"rockoartischocko">>}).
+
+req(Method, Url) ->
+    Headers = [?CONTENT_JSON, ?AUTH],
+    {ok, Code, _, Res} = test_request:request(Method, Url, Headers),
+    {Code, jiffy:decode(Res, [return_maps])}.
+
+req(copy, Url, Headers) ->
+    {ok, Code, _, Res} = test_request:request(copy, Url, Headers),
+    {Code, jiffy:decode(Res, [return_maps])};
+req(Method, Url, #{} = Body) ->
+    req(Method, Url, jiffy:encode(Body));
+req(Method, Url, Body) ->
+    Headers = [?CONTENT_JSON, ?AUTH],
+    {ok, Code, _, Res} = test_request:request(Method, Url, Headers, Body),
+    {Code, jiffy:decode(Res, [return_maps])}.
+
+wait_non_empty_chunk(Db) ->
+    test_util:wait(
+        fun() ->
+            {ok, _, _, Bin} = test_request:get(url(Db, 
"_changes?feed=live&timeout=1"), [?AUTH]),
+            Parts = binary:split(Bin, <<"\n">>, [global]),
+            case [P || P <- Parts, size(P) > 0] of
+                [] -> wait;
+                Chunks -> Chunks
             end
-        )}.
-
-should_return_ok_true_on_ensure_full_commit(Url0) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            Url = Url0 ++ "/_ensure_full_commit",
-            {ok, RC, _, Body} = test_request:post(Url, [?CONTENT_JSON, ?AUTH], 
[]),
-            {Json} = ?JSON_DECODE(Body),
-            ?assertEqual(201, RC),
-            ?assert(couch_util:get_value(<<"ok">>, Json))
-        end)}.
-
-should_return_404_for_ensure_full_commit_on_no_db(Url0) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            Url = Url0 ++ "-missing-db" ++ "/_ensure_full_commit",
-            {ok, RC, _, Body} = test_request:post(Url, [?CONTENT_JSON, ?AUTH], 
[]),
-            {Json} = ?JSON_DECODE(Body),
-            ?assertEqual(404, RC),
-            ?assertEqual(<<"not_found">>, couch_util:get_value(<<"error">>, 
Json))
-        end)}.
-
-should_accept_live_as_an_alias_for_continuous(Url) ->
-    GetLastSeq = fun(Chunks) ->
-        LastSeqBin = lists:last(Chunks),
-        {Result} =
-            try ?JSON_DECODE(LastSeqBin) of
-                Data -> Data
-            catch
-                _:_ ->
-                    % should not happen, abort
-                    ?assert(false)
-            end,
-        couch_util:get_value(<<"last_seq">>, Result, undefined)
-    end,
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            LastSeq1 = GetLastSeq(wait_non_empty_chunk(Url)),
-
-            {ok, _, _, _} = create_doc(Url, "testdoc2"),
-
-            LastSeq2 = GetLastSeq(wait_non_empty_chunk(Url)),
-
-            ?assertNotEqual(LastSeq1, LastSeq2)
-        end)}.
-
-should_return_404_for_delete_att_on_notadoc(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, RC, _, RespBody} = test_request:delete(
-                Url ++ "/notadoc/att.pdf",
-                [?CONTENT_JSON, ?AUTH],
-                []
-            ),
-            ?assertEqual(404, RC),
-            ?assertEqual(
-                {[
-                    {<<"error">>, <<"not_found">>},
-                    {<<"reason">>, <<"missing">>}
-                ]},
-                jiffy:decode(RespBody)
-            ),
-            {ok, RC1, _, _} = test_request:get(
-                Url ++ "/notadoc",
-                [?CONTENT_JSON, ?AUTH],
-                []
-            ),
-            ?assertEqual(404, RC1)
-        end)}.
-
-should_return_409_for_del_att_without_rev(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, RC, _, _} = test_request:put(
-                Url ++ "/testdoc3",
-                [?CONTENT_JSON, ?AUTH],
-                jiffy:encode(attachment_doc())
-            ),
-            ?assertEqual(201, RC),
-
-            {ok, RC1, _, _} = test_request:delete(
-                Url ++ "/testdoc3/file.erl",
-                [?CONTENT_JSON, ?AUTH],
-                []
-            ),
-            ?assertEqual(409, RC1)
-        end)}.
-
-should_return_200_for_del_att_with_rev(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, RC, _Headers, RespBody} = test_request:put(
-                Url ++ "/testdoc4",
-                [?CONTENT_JSON, ?AUTH],
-                jiffy:encode(attachment_doc())
-            ),
-            ?assertEqual(201, RC),
-
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            Rev = couch_util:get_value(<<"rev">>, ResultJson, undefined),
-
-            {ok, RC1, _, _} = test_request:delete(
-                Url ++ "/testdoc4/file.erl?rev=" ++ Rev,
-                [?CONTENT_JSON, ?AUTH],
-                []
-            ),
-            ?assertEqual(200, RC1)
-        end)}.
-
-should_return_409_for_put_att_nonexistent_rev(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, RC, _Headers, RespBody} = test_request:put(
-                Url ++ "/should_return_404/file.erl?rev=1-000",
-                [?CONTENT_JSON, ?AUTH],
-                jiffy:encode(attachment_doc())
-            ),
-            ?assertEqual(409, RC),
-            ?assertMatch(
-                {[
-                    {<<"error">>, <<"not_found">>},
-                    {<<"reason">>, <<"missing_rev">>}
-                ]},
-                ?JSON_DECODE(RespBody)
-            )
-        end)}.
-
-should_return_update_seq_when_set_on_all_docs(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)],
-            {ok, RC, _, RespBody} = test_request:get(
-                Url ++ "/_all_docs/" ++
-                    "?update_seq=true&keys=[\"testdoc1\"]",
-                [?CONTENT_JSON, ?AUTH]
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ?assertNotEqual(
-                undefined,
-                couch_util:get_value(<<"update_seq">>, ResultJson)
-            ),
-            ?assertNotEqual(
-                undefined,
-                couch_util:get_value(<<"offset">>, ResultJson)
-            )
-        end)}.
-
-should_not_return_update_seq_when_unset_on_all_docs(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)],
-            {ok, RC, _, RespBody} = test_request:get(
-                Url ++ "/_all_docs/" ++
-                    "?update_seq=false&keys=[\"testdoc1\"]",
-                [?CONTENT_JSON, ?AUTH]
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ?assertEqual(
-                undefined,
-                couch_util:get_value(<<"update_seq">>, ResultJson)
-            ),
-            ?assertNotEqual(
-                undefined,
-                couch_util:get_value(<<"offset">>, ResultJson)
-            )
-        end)}.
-
-should_return_correct_id_on_doc_copy(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, _, _, _} = create_doc(Url, "testdoc"),
-            {_, _, _, ResultBody1} = test_request:copy(
-                Url ++ "/testdoc/",
-                [?CONTENT_JSON, ?AUTH, ?DESTHEADER1]
-            ),
-            {ResultJson1} = ?JSON_DECODE(ResultBody1),
-            Id1 = couch_util:get_value(<<"id">>, ResultJson1),
-
-            {_, _, _, ResultBody2} = test_request:copy(
-                Url ++ "/testdoc/",
-                [?CONTENT_JSON, ?AUTH, ?DESTHEADER2]
-            ),
-            {ResultJson2} = ?JSON_DECODE(ResultBody2),
-            Id2 = couch_util:get_value(<<"id">>, ResultJson2),
-            [
-                ?assertEqual(<<102, 111, 111, 229, 149, 138, 98, 97, 114>>, 
Id1),
-                ?assertEqual(<<"foo/bar#baz?pow:fiz">>, Id2)
-            ]
-        end)}.
-
-should_return_only_one_ok_on_doc_copy(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, _, _, _} = create_doc(Url, "testdoc"),
-            {_, _, _, ResultBody} = test_request:copy(
-                Url ++ "/testdoc",
-                [?CONTENT_JSON, ?AUTH, ?DESTHEADER1]
-            ),
-            {ResultJson} = jiffy:decode(ResultBody),
-            NumOks = length(lists:filter(fun({Key, _Value}) -> Key == <<"ok">> 
end, ResultJson)),
-            [
-                ?assertEqual(1, NumOks)
-            ]
-        end)}.
+        end
+    ).
 
 attachment_doc() ->
     {ok, Data} = file:read_file(?FIXTURE_TXT),
-    {[
-        {<<"_attachments">>,
-            {[
-                {<<"file.erl">>,
-                    {[
-                        {<<"content_type">>, <<"text/plain">>},
-                        {<<"data">>, base64:encode(Data)}
-                    ]}}
-            ]}}
-    ]}.
-
-should_return_400_for_bad_engine(_) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            TmpDb = ?tempdb(),
-            Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
-            Port = mochiweb_socket_server:get(chttpd, port),
-            BaseUrl = lists:concat(["http://";, Addr, ":", Port, "/", 
?b2l(TmpDb)]),
-            Url = BaseUrl ++ "?engine=cowabunga",
-            {ok, Status, _, _} = test_request:put(Url, [?CONTENT_JSON, ?AUTH], 
"{}"),
-            ?assertEqual(400, Status)
-        end)}.
-
-should_not_change_db_proper_after_rewriting_shardmap(_) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            TmpDb = ?tempdb(),
-            Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
-            Port = mochiweb_socket_server:get(chttpd, port),
-
-            BaseUrl = lists:concat(["http://";, Addr, ":", Port, "/", 
?b2l(TmpDb)]),
-            Url = BaseUrl ++ "?partitioned=true&q=1",
-            {ok, 201, _, _} = test_request:put(Url, [?CONTENT_JSON, ?AUTH], 
"{}"),
-
-            ShardDbName = ?l2b(config:get("mem3", "shards_db", "_dbs")),
-            {ok, ShardDb} = mem3_util:ensure_exists(ShardDbName),
-            {ok, #doc{body = {Props}}} = couch_db:open_doc(
-                ShardDb, TmpDb, [ejson_body]
-            ),
-            Shards = mem3_util:build_shards(TmpDb, Props),
-
-            {Prop2} = ?JSON_DECODE(?JSON_ENCODE({Props})),
-            Shards2 = mem3_util:build_shards(TmpDb, Prop2),
-            ?assertEqual(Shards2, Shards),
-            {ok, 200, _, _} = test_request:delete(BaseUrl, [?AUTH])
-        end)}.
-
-should_succeed_on_all_docs_with_queries_keys(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
-            QueryDoc = "{\"queries\": [{\"keys\": [ \"testdoc3\", 
\"testdoc8\"]}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++ "/_all_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_all_docs_with_queries_limit_skip(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
-            QueryDoc = "{\"queries\": [{\"limit\": 5, \"skip\": 2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++ "/_all_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson)),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_all_docs_with_multiple_queries(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)],
-            QueryDoc =
-                "{\"queries\": [{\"keys\": [ \"testdoc3\", \"testdoc8\"]},\n"
-                "            {\"limit\": 5, \"skip\": 2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++ "/_all_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson1} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson1))),
-            {InnerJson2} = lists:nth(2, ResultJsonBody),
-            ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson2)),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson2)))
-        end)}.
-
-should_succeed_on_design_docs_with_queries_keys(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc =
-                "{\"queries\": [{\"keys\": [ \"_design/ddoc3\",\n"
-                "            \"_design/ddoc8\"]}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++
-                    "/_design_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_design_docs_with_queries_limit_skip(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc = "{\"queries\": [{\"limit\": 5, \"skip\": 2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++
-                    "/_design_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson)),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_design_docs_with_multiple_queries(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc =
-                "{\"queries\": [{\"keys\": [ \"_design/ddoc3\",\n"
-                "            \"_design/ddoc8\"]}, {\"limit\": 5, \"skip\": 
2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++
-                    "/_design_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson1} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson1))),
-            {InnerJson2} = lists:nth(2, ResultJsonBody),
-            ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson2)),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson2)))
-        end)}.
-
-should_succeed_on_local_docs_with_queries_keys(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_local/doc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc =
-                "{\"queries\": [{\"keys\":\n"
-                "            [ \"_local/doc3\", \"_local/doc8\"]}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++ "/_local_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_local_docs_with_queries_limit_skip(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_local/doc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc = "{\"queries\": [{\"limit\": 5, \"skip\": 2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++
-                    "/_local_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson)))
-        end)}.
-
-should_succeed_on_local_docs_with_multiple_queries(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            [create_doc(Url, "_local/doc" ++ ?i2l(I)) || I <- lists:seq(1, 
10)],
-            QueryDoc =
-                "{\"queries\": [{\"keys\": [ \"_local/doc3\",\n"
-                "            \"_local/doc8\"]}, {\"limit\": 5, \"skip\": 2}]}",
-            {ok, RC, _, RespBody} = test_request:post(
-                Url ++
-                    "/_local_docs/queries/",
-                [?CONTENT_JSON, ?AUTH],
-                QueryDoc
-            ),
-            ?assertEqual(200, RC),
-            {ResultJson} = ?JSON_DECODE(RespBody),
-            ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson),
-            {InnerJson1} = lists:nth(1, ResultJsonBody),
-            ?assertEqual(2, length(couch_util:get_value(<<"rows">>, 
InnerJson1))),
-            {InnerJson2} = lists:nth(2, ResultJsonBody),
-            ?assertEqual(5, length(couch_util:get_value(<<"rows">>, 
InnerJson2)))
-        end)}.
-
-should_return_headers_after_starting_continious(Url) ->
-    {timeout, ?TIMEOUT,
-        ?_test(begin
-            {ok, _, _, Bin} =
-                test_request:get(Url ++ "/_changes?feed=live&timeout=1", 
[?AUTH]),
-
-            Parts = binary:split(Bin, <<"\n">>, [global]),
-            %% we should receive at least one part even when timeout=1
-            ?assertNotEqual([], Parts)
-        end)}.
-
-wait_non_empty_chunk(Url) ->
-    test_util:wait(fun() ->
-        {ok, _, _, Bin} =
-            test_request:get(Url ++ "/_changes?feed=live&timeout=1", [?AUTH]),
-
-        Parts = binary:split(Bin, <<"\n">>, [global]),
-
-        case [P || P <- Parts, size(P) > 0] of
-            [] -> wait;
-            Chunks -> Chunks
-        end
-    end).
+    #{
+        <<"_attachments">> => #{
+            <<"file.erl">> => #{
+                <<"content_type">> => <<"text/plain">>,
+                <<"data">> => base64:encode(Data)
+            }
+        }
+    }.
diff --git a/src/fabric/src/fabric_view_all_docs.erl 
b/src/fabric/src/fabric_view_all_docs.erl
index 7821dcf72..a6786bff7 100644
--- a/src/fabric/src/fabric_view_all_docs.erl
+++ b/src/fabric/src/fabric_view_all_docs.erl
@@ -73,6 +73,15 @@ go(DbName, Options, QueryArgs, Callback, Acc0) ->
         spawn_monitor(?MODULE, open_doc, [DbName, Options ++ DocOptions1, Key, 
IncludeDocs])
     end,
     MaxJobs = all_docs_concurrency(),
+    %% namespace can be _set_ to `undefined`, so we want simulate enum here
+    Namespace =
+        case couch_util:get_value(namespace, Extra) of
+            <<"_all_docs">> -> <<"_all_docs">>;
+            <<"_design">> -> <<"_design">>;
+            <<"_non_design">> -> <<"_non_design">>;
+            <<"_local">> -> <<"_local">>;
+            _ -> <<"_all_docs">>
+        end,
     Keys1 =
         case Dir of
             fwd -> Keys0;
@@ -88,15 +97,7 @@ go(DbName, Options, QueryArgs, Callback, Acc0) ->
             true -> lists:sublist(Keys2, Limit);
             false -> Keys2
         end,
-    %% namespace can be _set_ to `undefined`, so we want simulate enum here
-    Namespace =
-        case couch_util:get_value(namespace, Extra) of
-            <<"_all_docs">> -> <<"_all_docs">>;
-            <<"_design">> -> <<"_design">>;
-            <<"_non_design">> -> <<"_non_design">>;
-            <<"_local">> -> <<"_local">>;
-            _ -> <<"_all_docs">>
-        end,
+    Keys4 = filter_keys_by_namespace(Keys3, Namespace),
     Timeout = fabric_util:all_docs_timeout(),
     {_, Ref} = spawn_monitor(fun() ->
         exit(fabric:get_doc_count(DbName, Namespace))
@@ -112,7 +113,7 @@ go(DbName, Options, QueryArgs, Callback, Acc0) ->
                 end,
             {ok, Acc1} = Callback({meta, Meta}, Acc0),
             Resp = doc_receive_loop(
-                Keys3, queue:new(), SpawnFun, MaxJobs, Callback, Acc1
+                Keys4, queue:new(), SpawnFun, MaxJobs, Callback, Acc1
             ),
             case Resp of
                 {ok, Acc2} ->
@@ -361,3 +362,26 @@ cancel_read_pids(Pids) ->
         {empty, _} ->
             ok
     end.
+
+filter_keys_by_namespace(Keys, Namespace) when Namespace =:= <<"_design">> ->
+    lists:filter(
+        fun(Key) ->
+            case Key of
+                <<?DESIGN_DOC_PREFIX, _/binary>> -> true;
+                _ -> false
+            end
+        end,
+        Keys
+    );
+filter_keys_by_namespace(Keys, Namespace) when Namespace =:= <<"_local">> ->
+    lists:filter(
+        fun(Key) ->
+            case Key of
+                <<?LOCAL_DOC_PREFIX, _/binary>> -> true;
+                _ -> false
+            end
+        end,
+        Keys
+    );
+filter_keys_by_namespace(Keys, _Namespace) ->
+    Keys.


Reply via email to