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

jiahuili430 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 2d12ab09d Avoid read docs twice when filtered `_changes` is triggered 
(#4862)
2d12ab09d is described below

commit 2d12ab09dc21f32beb04c603d32aa74ad3cbd2f8
Author: Jiahui Li <[email protected]>
AuthorDate: Tue Mar 12 14:07:31 2024 -0500

    Avoid read docs twice when filtered `_changes` is triggered (#4862)
    
    * Add more tests for couch_changes and fabric changes
    
    Add more tests to ensure the response is the same before and after
    the code change.
    
    Code coverage improvements
    - chttpd_changes_test.erl: 99% -> 100%
    - couch_changes.erl: 80% -> 82%
    - fabric_rpc.erl: 56% -> 61%
    
    * Verify the number of calls to `open_doc/3` when requesting `_changes`
    
    * Avoid read docs twice when filtered _changes is triggered
    
    - When filered `_changes` is triggered, `open_revs()` will
    read docs. If request parameter has `include_docs=true`,
    then `doc_member()` will also read docs.
    
    To avoid the above behavior, changed `filter/3` to `filter/5`.
    
    - When using filtered `_changes` with `style=all_docs`,
    `conflicts=true` and `include_docs=true`, the response should
    contain the `_conflicts` field.
    
    Add `open_all_revs_include_doc/2` to include `_conflicts` field.
    
    - If conflicted docs contain a deleted doc, the deleted `rev` value
    should not appear in the `doc` field.
    
    Add `Doc#doc.deleted =:= false` filter to avoid this behaviour.
    
    * Update tests to verify the number of calls to `open_doc/3`
---
 src/chttpd/test/eunit/chttpd_changes_test.erl |  81 ++-
 src/couch/src/couch_changes.erl               | 112 +++-
 src/couch/test/eunit/couch_changes_tests.erl  | 905 +++++++++++++++++++++++---
 src/fabric/src/fabric_rpc.erl                 |  61 +-
 src/fabric/test/eunit/fabric_changes_test.erl | 558 ++++++++++++++++
 5 files changed, 1542 insertions(+), 175 deletions(-)

diff --git a/src/chttpd/test/eunit/chttpd_changes_test.erl 
b/src/chttpd/test/eunit/chttpd_changes_test.erl
index b08eb65fd..99ae0495f 100644
--- a/src/chttpd/test/eunit/chttpd_changes_test.erl
+++ b/src/chttpd/test/eunit/chttpd_changes_test.erl
@@ -153,6 +153,18 @@ changes_include_docs_test_() ->
         ]
     }.
 
+changes_open_doc_times_test_() ->
+    {
+        foreach,
+        fun setup_basic/0,
+        fun teardown_basic/1,
+        [
+            ?TDEF_FE(t_selector_open_doc_times),
+            ?TDEF_FE(t_js_filter_open_doc_times),
+            ?TDEF_FE(t_view_open_doc_times)
+        ]
+    }.
+
 t_basic({_, DbUrl}) ->
     Res = {Seq, Pending, Rows} = changes(DbUrl),
     ?assertEqual(8, Seq),
@@ -853,8 +865,39 @@ t_view_all_docs_conflicts_include_docs({_, DbUrl}) ->
     ?assertEqual(Res1, Res2),
     delete_ddocs(DDocUrl, Rev).
 
-% Utility functions
+t_selector_open_doc_times({_, DbUrl}) ->
+    Body = #{<<"selector">> => #{<<"_id">> => ?DOC1}},
+    F = "?filter=_selector",
+    I = "&include_docs=true",
+    S = "&style=all_docs",
+    Filter = called_times(fun() -> changes_post(DbUrl, Body, F) end, DbUrl),
+    FilterDocs = called_times(fun() -> changes_post(DbUrl, Body, F ++ I) end, 
DbUrl),
+    FilterAllDocs = called_times(fun() -> changes_post(DbUrl, Body, F ++ I ++ 
S) end, DbUrl),
+    ?assertEqual({3, 3, 5}, {Filter, FilterDocs, FilterAllDocs}).
+
+t_js_filter_open_doc_times({_, DbUrl}) ->
+    {DDocUrl, Rev} = create_ddocs(DbUrl, ?DOC1, custom),
+    F = "?filter=filters/f",
+    I = "&include_docs=true",
+    S = "&style=all_docs",
+    Filter = called_times(fun() -> changes(DbUrl, F) end, DbUrl),
+    FilterDocs = called_times(fun() -> changes(DbUrl, F ++ I) end, DbUrl),
+    FilterAllDocs = called_times(fun() -> changes(DbUrl, F ++ I ++ S) end, 
DbUrl),
+    ?assertEqual({4, 4, 6}, {Filter, FilterDocs, FilterAllDocs}),
+    delete_ddocs(DDocUrl, Rev).
 
+t_view_open_doc_times({_, DbUrl}) ->
+    {DDocUrl, Rev} = create_ddocs(DbUrl, ?DOC1, view),
+    F = "?filter=_view&view=views/v",
+    I = "&include_docs=true",
+    S = "&style=all_docs",
+    Filter = called_times(fun() -> changes(DbUrl, F) end, DbUrl),
+    FilterDocs = called_times(fun() -> changes(DbUrl, F ++ I) end, DbUrl),
+    FilterAllDocs = called_times(fun() -> changes(DbUrl, F ++ S ++ I) end, 
DbUrl),
+    ?assertEqual({4, 4, 6}, {Filter, FilterDocs, FilterAllDocs}),
+    delete_ddocs(DDocUrl, Rev).
+
+% Utility functions
 setup_ctx(DbCreateParams) ->
     Ctx = test_util:start_couch([chttpd]),
     Hashed = couch_passwords:hash_admin_password(?PASS),
@@ -883,6 +926,7 @@ setup_basic() ->
     CfgKey = "changes_doc_ids_optimization_threshold",
     ok = config:set("couchdb", CfgKey, "2", _Persist = false),
     meck:new(couch_changes, [passthrough]),
+    meck:new(couch_db, [passthrough]),
     {Ctx, DbUrl}.
 
 teardown_basic({Ctx, DbUrl}) ->
@@ -891,20 +935,11 @@ teardown_basic({Ctx, DbUrl}) ->
     teardown_ctx({Ctx, DbUrl}).
 
 create_db(Top, Db, Params) ->
-    case req(put, Top ++ Db ++ Params) of
-        {201, #{}} ->
-            ok;
-        Error ->
-            error({failed_to_create_test_db, Db, Error})
-    end.
+    {201, #{}} = req(put, Top ++ Db ++ Params),
+    ok.
 
 delete_db(DbUrl) ->
-    case req(delete, DbUrl) of
-        {200, #{}} ->
-            ok;
-        Error ->
-            error({failed_to_delete_test_db, DbUrl, Error})
-    end.
+    {200, #{}} = req(delete, DbUrl).
 
 doc_fun({Id, Revs, Deleted}) ->
     Doc = #{
@@ -1033,3 +1068,23 @@ seq(<<_/binary>> = Seq) ->
     binary_to_integer(NumStr);
 seq(null) ->
     null.
+
+called_times(ReqFun, DbUrl) ->
+    meck:reset(couch_db),
+    ReqFun(),
+    open_doc_calls(DbUrl).
+
+open_doc_calls(DbUrl) ->
+    #{path := "/" ++ DbName0} = uri_string:parse(DbUrl),
+    DbName = ?l2b(DbName0),
+    FoldFun =
+        fun([Db, IdOrDocInfo, _Opts], Acc) ->
+            case {mem3:dbname(couch_db:name(Db)), IdOrDocInfo} of
+                {DbName, #doc_info{}} -> Acc + 1;
+                _ -> Acc
+            end
+        end,
+    lists:foldl(FoldFun, 0, meck_history(couch_db, open_doc, 3)).
+
+meck_history(Mod, Fun, Arity) ->
+    [A || {_Pid, {_M, F, A}, _R} <- meck:history(Mod), F =:= Fun, length(A) 
=:= Arity].
diff --git a/src/couch/src/couch_changes.erl b/src/couch/src/couch_changes.erl
index e072a2e1c..6299cf451 100644
--- a/src/couch/src/couch_changes.erl
+++ b/src/couch/src/couch_changes.erl
@@ -19,7 +19,7 @@
     wait_updated/3,
     get_rest_updated/1,
     configure_filter/4,
-    filter/3,
+    filter/5,
     handle_db_event/3,
     handle_view_event/3,
     send_changes_doc_ids/6,
@@ -225,44 +225,71 @@ configure_filter(FilterName, Style, Req, Db) ->
             throw({bad_request, Msg})
     end.
 
-filter(Db, #full_doc_info{} = FDI, Filter) ->
-    filter(Db, couch_doc:to_doc_info(FDI), Filter);
-filter(_Db, DocInfo, {default, Style}) ->
-    apply_style(DocInfo, Style);
-filter(_Db, DocInfo, {doc_ids, Style, DocIds}) ->
-    case lists:member(DocInfo#doc_info.id, DocIds) of
-        true ->
-            apply_style(DocInfo, Style);
-        false ->
-            []
-    end;
-filter(Db, DocInfo, {selector, Style, {Selector, _Fields}}) ->
-    Docs = open_revs(Db, DocInfo, Style),
-    Passes = [
-        mango_selector:match(Selector, couch_doc:to_json_obj(Doc, []))
-     || Doc <- Docs
-    ],
-    filter_revs(Passes, Docs);
-filter(_Db, DocInfo, {design_docs, Style}) ->
-    case DocInfo#doc_info.id of
-        <<"_design", _/binary>> ->
-            apply_style(DocInfo, Style);
-        _ ->
-            []
+filter(Db, #full_doc_info{} = FDI, Filter, IncludeDocs, Conflicts) ->
+    filter(Db, couch_doc:to_doc_info(FDI), Filter, IncludeDocs, Conflicts);
+filter(_Db, DocInfo, {default, Style}, _IncludeDocs, _Conflicts) ->
+    {[], apply_style(DocInfo, Style)};
+filter(_Db, DocInfo, {doc_ids, Style, DocIds}, _IncludeDocs, _Conflicts) ->
+    Revs =
+        case lists:member(DocInfo#doc_info.id, DocIds) of
+            true ->
+                apply_style(DocInfo, Style);
+            false ->
+                []
+        end,
+    {[], Revs};
+filter(_Db, DocInfo, {design_docs, Style}, _IncludeDocs, _Conflicts) ->
+    Revs =
+        case DocInfo#doc_info.id of
+            <<"_design", _/binary>> ->
+                apply_style(DocInfo, Style);
+            _ ->
+                []
+        end,
+    {[], Revs};
+filter(Db, DocInfo, {selector, all_docs, {Selector, _Fields}}, true, true) ->
+    {DocWin, Docs} = open_all_revs_include_doc(Db, DocInfo),
+    Passes = [mango_selector:match(Selector, couch_doc:to_json_obj(Doc, [])) 
|| Doc <- Docs],
+    {DocWin, filter_revs(Passes, Docs)};
+filter(Db, DocInfo, {selector, Style, {Selector, _Fields}}, IncludeDocs, 
Conflicts) ->
+    Docs = open_revs(Db, DocInfo, Style, Conflicts),
+    Passes = [mango_selector:match(Selector, couch_doc:to_json_obj(Doc, [])) 
|| Doc <- Docs],
+    case IncludeDocs of
+        true -> {Docs, filter_revs(Passes, Docs)};
+        false -> {[], filter_revs(Passes, Docs)}
     end;
-filter(Db, DocInfo, {view, Style, DDoc, VName}) ->
-    Docs = open_revs(Db, DocInfo, Style),
+filter(Db, DocInfo, {view, all_docs, DDoc, VName}, true, true) ->
+    {DocWin, Docs} = open_all_revs_include_doc(Db, DocInfo),
     {ok, Passes} = couch_query_servers:filter_view(Db, DDoc, VName, Docs),
-    filter_revs(Passes, Docs);
-filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
+    {DocWin, filter_revs(Passes, Docs)};
+filter(Db, DocInfo, {view, Style, DDoc, VName}, IncludeDocs, Conflicts) ->
+    Docs = open_revs(Db, DocInfo, Style, Conflicts),
+    {ok, Passes} = couch_query_servers:filter_view(Db, DDoc, VName, Docs),
+    case IncludeDocs of
+        true -> {Docs, filter_revs(Passes, Docs)};
+        false -> {[], filter_revs(Passes, Docs)}
+    end;
+filter(Db, DocInfo, {custom, all_docs, Req0, DDoc, FName}, true, true) ->
+    Req =
+        case Req0 of
+            {json_req, _} -> Req0;
+            #httpd{} -> {json_req, chttpd_external:json_req_obj(Req0, Db)}
+        end,
+    {DocWin, Docs} = open_all_revs_include_doc(Db, DocInfo),
+    {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
+    {DocWin, filter_revs(Passes, Docs)};
+filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}, IncludeDocs, 
Conflicts) ->
     Req =
         case Req0 of
             {json_req, _} -> Req0;
             #httpd{} -> {json_req, chttpd_external:json_req_obj(Req0, Db)}
         end,
-    Docs = open_revs(Db, DocInfo, Style),
+    Docs = open_revs(Db, DocInfo, Style, Conflicts),
     {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
-    filter_revs(Passes, Docs).
+    case IncludeDocs of
+        true -> {Docs, filter_revs(Passes, Docs)};
+        false -> {[], filter_revs(Passes, Docs)}
+    end.
 
 get_view_qs({json_req, {Props}}) ->
     {Query} = couch_util:get_value(<<"query">>, Props, {[]}),
@@ -357,17 +384,32 @@ apply_style(#doc_info{revs = Revs}, main_only) ->
 apply_style(#doc_info{revs = Revs}, all_docs) ->
     [{[{<<"rev">>, couch_doc:rev_to_str(R)}]} || #rev_info{rev = R} <- Revs].
 
-open_revs(Db, DocInfo, Style) ->
+open_revs(Db, DocInfo, Style, Conflicts) ->
     DocInfos =
         case Style of
             main_only -> [DocInfo];
             all_docs -> [DocInfo#doc_info{revs = [R]} || R <- 
DocInfo#doc_info.revs]
         end,
-    OpenOpts = [deleted, conflicts],
+    OpenOpts =
+        case Conflicts of
+            true -> [deleted, conflicts];
+            false -> [deleted]
+        end,
     % Relying on list comprehensions to silence errors
     OpenResults = [couch_db:open_doc(Db, DI, OpenOpts) || DI <- DocInfos],
     [Doc || {ok, Doc} <- OpenResults].
 
+open_all_revs_include_doc(Db, DocInfo) ->
+    DocInfos = [DocInfo#doc_info{revs = [R]} || R <- DocInfo#doc_info.revs],
+    OpenOpts = [deleted, conflicts],
+    OpenResults = [couch_db:open_doc(Db, DI, OpenOpts) || DI <- DocInfos],
+    Docs = [Doc || {ok, Doc} <- OpenResults],
+    [Doc1 | RestDocs] = Docs,
+    RestRevs = [Doc#doc.revs || Doc <- RestDocs, Doc#doc.deleted =:= false],
+    Conflicts = [{conflicts, [{Pos, RevId} || {Pos, [RevId]} <- RestRevs]}],
+    Doc2 = Doc1#doc{meta = Conflicts},
+    {[Doc2], Docs}.
+
 filter_revs(Passes, Docs) ->
     lists:flatmap(
         fun
@@ -611,12 +653,14 @@ changes_enumerator(Value, Acc) ->
         prepend = Prepend,
         user_acc = UserAcc,
         limit = Limit,
+        include_docs = IncludeDocs,
+        conflicts = Conflicts,
         resp_type = ResponseType,
         db = Db,
         timeout = Timeout,
         timeout_fun = TimeoutFun
     } = maybe_upgrade_changes_acc(Acc),
-    Results0 = filter(Db, Value, Filter),
+    {_, Results0} = filter(Db, Value, Filter, IncludeDocs, Conflicts),
     Results = [Result || Result <- Results0, Result /= null],
     Seq =
         case Value of
diff --git a/src/couch/test/eunit/couch_changes_tests.erl 
b/src/couch/test/eunit/couch_changes_tests.erl
index 59d564e91..bbeeac26a 100644
--- a/src/couch/test/eunit/couch_changes_tests.erl
+++ b/src/couch/test/eunit/couch_changes_tests.erl
@@ -16,6 +16,43 @@
 -include_lib("couch/include/couch_db.hrl").
 
 -define(TIMEOUT, 6000).
+-define(ViewDDoc,
+    couch_doc:from_json_obj(
+        {[
+            {<<"_id">>, <<"_design/app">>},
+            {<<"language">>, <<"javascript">>},
+            {<<"views">>,
+                {[
+                    {<<"valid">>,
+                        {[
+                            {<<"map">>, <<
+                                "function(doc) {"
+                                " if (doc._id == 'doc1') {"
+                                " emit(doc); "
+                                "} }"
+                            >>}
+                        ]}}
+                ]}}
+        ]}
+    )
+).
+-define(CustomDDoc,
+    couch_doc:from_json_obj(
+        {[
+            {<<"_id">>, <<"_design/app">>},
+            {<<"language">>, <<"javascript">>},
+            {<<"filters">>,
+                {[
+                    {<<"valid">>, <<
+                        "function(doc) {"
+                        " if (doc._id == 'doc1') {"
+                        " return true; "
+                        "} }"
+                    >>}
+                ]}}
+        ]}
+    )
+).
 
 -record(row, {
     id,
@@ -79,7 +116,6 @@ changes_test_() ->
                 filter_by_custom_function(),
                 filter_by_filter_function(),
                 filter_by_view(),
-                style_and_include_docs(),
                 style_and_include_docs_with_revtree()
             ]
         }
@@ -186,22 +222,6 @@ continuous_feed() ->
         }
     }.
 
-style_and_include_docs() ->
-    {
-        "Style and include_docs",
-        {
-            foreach,
-            fun setup/0,
-            fun teardown/1,
-            [
-                ?TDEF_FE(t_style_main_only),
-                ?TDEF_FE(t_style_main_only_with_include_docs),
-                ?TDEF_FE(t_style_all_docs),
-                ?TDEF_FE(t_style_all_docs_with_include_docs)
-            ]
-        }
-    }.
-
 style_and_include_docs_with_revtree() ->
     {
         "Style and include_docs with revtree",
@@ -211,9 +231,39 @@ style_and_include_docs_with_revtree() ->
             fun teardown/1,
             [
                 ?TDEF_FE(t_style_main_only_with_revtree),
-                ?TDEF_FE(t_style_main_only_with_include_docs_with_revtree),
                 ?TDEF_FE(t_style_all_docs_with_revtree),
-                ?TDEF_FE(t_style_all_docs_with_include_docs_with_revtree)
+                ?TDEF_FE(t_style_main_only_with_include_docs_with_revtree),
+                ?TDEF_FE(t_style_all_docs_with_include_docs_with_revtree),
+                
?TDEF_FE(t_style_main_only_with_include_docs_conflicts_with_revtree),
+                
?TDEF_FE(t_style_all_docs_with_include_docs_conflicts_with_revtree),
+
+                ?TDEF_FE(t_doc_ids_style_main_only_with_revtree),
+                ?TDEF_FE(t_doc_ids_style_all_docs_with_revtree),
+                
?TDEF_FE(t_doc_ids_style_main_only_with_include_docs_with_revtree),
+                
?TDEF_FE(t_doc_ids_style_all_docs_with_include_docs_with_revtree),
+                
?TDEF_FE(t_doc_ids_style_main_only_with_include_docs_conflicts_with_revtree),
+                
?TDEF_FE(t_doc_ids_style_all_docs_with_include_docs_conflicts_with_revtree),
+
+                ?TDEF_FE(t_selector_style_main_only_with_revtree),
+                ?TDEF_FE(t_selector_style_all_docs_with_revtree),
+                
?TDEF_FE(t_selector_style_main_only_with_include_docs_with_revtree),
+                
?TDEF_FE(t_selector_style_all_docs_with_include_docs_with_revtree),
+                
?TDEF_FE(t_selector_style_main_only_with_include_docs_conflicts_with_revtree),
+                
?TDEF_FE(t_selector_style_all_docs_with_include_docs_conflicts_with_revtree),
+
+                ?TDEF_FE(t_view_style_main_only_with_revtree),
+                ?TDEF_FE(t_view_style_all_docs_with_revtree),
+                
?TDEF_FE(t_view_style_main_only_with_include_docs_with_revtree),
+                ?TDEF_FE(t_view_style_all_docs_with_include_docs_with_revtree),
+                
?TDEF_FE(t_view_style_main_only_with_include_docs_conflicts_with_revtree),
+                
?TDEF_FE(t_view_style_all_docs_with_include_docs_conflicts_with_revtree),
+
+                ?TDEF_FE(t_custom_style_main_only_with_revtree),
+                ?TDEF_FE(t_custom_style_all_docs_with_revtree),
+                
?TDEF_FE(t_custom_style_main_only_with_include_docs_with_revtree),
+                
?TDEF_FE(t_custom_style_all_docs_with_include_docs_with_revtree),
+                
?TDEF_FE(t_custom_style_main_only_with_include_docs_conflicts_with_revtree),
+                
?TDEF_FE(t_custom_style_all_docs_with_include_docs_conflicts_with_revtree)
             ]
         }
     }.
@@ -538,10 +588,9 @@ t_receive_heartbeats(_) ->
     ?assert(Heartbeats3 > Heartbeats2).
 
 t_filter_by_doc_attribute({DbName, _}) ->
-    DDocId = <<"_design/app">>,
     DDoc = couch_doc:from_json_obj(
         {[
-            {<<"_id">>, DDocId},
+            {<<"_id">>, <<"_design/app">>},
             {<<"language">>, <<"javascript">>},
             {<<"filters">>,
                 {[
@@ -647,120 +696,105 @@ t_filter_by_erlang_view({DbName, _}) ->
     ?assertMatch([#row{seq = 6, id = <<"doc3">>}], Rows),
     ?assertEqual(UpSeq, LastSeq).
 
-t_style_main_only({DbName, _}) ->
+t_style_main_only_with_revtree({DbName, _}) ->
     ChArgs = #changes_args{style = main_only},
     Req = {json_req, null},
     {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
-    ?assertEqual(9, length(Rows)),
-    ?assertEqual(UpSeq, LastSeq),
-    ?assertMatch(
+    ?assertEqual(2, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
         [
-            #row{seq = 1, id = <<"doc1">>},
-            #row{seq = 2, id = <<"doc2">>},
-            #row{seq = 4, id = <<"doc4">>},
-            #row{seq = 5, id = <<"doc5">>},
-            #row{seq = 6, id = <<"doc3">>},
-            #row{seq = 7, id = <<"doc6">>},
-            #row{seq = 8, id = <<"_design/foo">>},
-            #row{seq = 9, id = <<"doc7">>},
-            #row{seq = 10, id = <<"doc8">>}
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc = nil,
+                revs = [<<"2-y">>]
+            },
+            #row{
+                seq = 4,
+                id = <<"doc2">>,
+                deleted = true,
+                doc = nil,
+                revs = [<<"1-m">>]
+            }
         ],
         Rows
     ).
 
-t_style_main_only_with_include_docs({DbName, Revs}) ->
-    ChArgs = #changes_args{style = main_only, include_docs = true},
-    Req = {json_req, null},
-    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
-    ?assertEqual(9, length(Rows)),
-    ?assertEqual(UpSeq, LastSeq),
-    FirstRev = element(1, Revs),
-    [FirstRow | _] = Rows,
-    ?assertMatch(
-        #row{
-            seq = 1,
-            id = <<"doc1">>,
-            doc = {[{<<"_id">>, <<"doc1">>}, {<<"_rev">>, FirstRev}]}
-        },
-        FirstRow
-    ).
-
-t_style_all_docs({DbName, _}) ->
+t_style_all_docs_with_revtree({DbName, _}) ->
     ChArgs = #changes_args{style = all_docs},
     Req = {json_req, null},
     {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
-    ?assertEqual(9, length(Rows)),
-    ?assertEqual(UpSeq, LastSeq),
-    ?assertMatch(
+    ?assertEqual(2, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
         [
-            #row{seq = 1, id = <<"doc1">>},
-            #row{seq = 2, id = <<"doc2">>},
-            #row{seq = 4, id = <<"doc4">>},
-            #row{seq = 5, id = <<"doc5">>},
-            #row{seq = 6, id = <<"doc3">>},
-            #row{seq = 7, id = <<"doc6">>},
-            #row{seq = 8, id = <<"_design/foo">>},
-            #row{seq = 9, id = <<"doc7">>},
-            #row{seq = 10, id = <<"doc8">>}
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            },
+            #row{
+                seq = 4,
+                id = <<"doc2">>,
+                deleted = true,
+                revs = [<<"1-m">>, <<"1-l">>]
+            }
         ],
         Rows
     ).
 
-t_style_all_docs_with_include_docs({DbName, Revs}) ->
-    ChArgs = #changes_args{style = all_docs, include_docs = true},
-    Req = {json_req, null},
-    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
-    ?assertEqual(9, length(Rows)),
-    ?assertEqual(UpSeq, LastSeq),
-    FirstRev = element(1, Revs),
-    [FirstRow | _] = Rows,
-    ?assertMatch(
-        #row{
-            seq = 1,
-            id = <<"doc1">>,
-            doc = {[{<<"_id">>, <<"doc1">>}, {<<"_rev">>, FirstRev}]}
-        },
-        FirstRow
-    ).
-
-t_style_main_only_with_revtree({DbName, _}) ->
-    ChArgs = #changes_args{style = main_only},
+t_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        include_docs = true
+    },
     Req = {json_req, null},
     {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
     ?assertEqual(2, length(Rows)),
     ?assertEqual(4, LastSeq),
     ?assertEqual(4, UpSeq),
-    ?assertMatch(
+    ?assertEqual(
         [
             #row{
                 seq = 3,
                 id = <<"doc1">>,
-                doc = nil,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
                 revs = [<<"2-y">>]
             },
             #row{
                 seq = 4,
                 id = <<"doc2">>,
                 deleted = true,
-                doc = nil,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc2">>},
+                        {<<"_rev">>, <<"1-m">>},
+                        {<<"_deleted">>, true}
+                    ]},
                 revs = [<<"1-m">>]
             }
         ],
         Rows
     ).
 
-t_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+t_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
     ChArgs = #changes_args{
-        style = main_only,
-        include_docs = true,
-        conflicts = true
+        style = all_docs,
+        include_docs = true
     },
     Req = {json_req, null},
     {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
     ?assertEqual(2, length(Rows)),
     ?assertEqual(4, LastSeq),
     ?assertEqual(4, UpSeq),
-    ?assertMatch(
+    ?assertEqual(
         [
             #row{
                 seq = 3,
@@ -768,10 +802,9 @@ t_style_main_only_with_include_docs_with_revtree({DbName, 
_}) ->
                 doc =
                     {[
                         {<<"_id">>, <<"doc1">>},
-                        {<<"_rev">>, <<"2-y">>},
-                        {<<"_conflicts">>, [<<"2-x">>]}
+                        {<<"_rev">>, <<"2-y">>}
                     ]},
-                revs = [<<"2-y">>]
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
             },
             #row{
                 seq = 4,
@@ -783,37 +816,53 @@ t_style_main_only_with_include_docs_with_revtree({DbName, 
_}) ->
                         {<<"_rev">>, <<"1-m">>},
                         {<<"_deleted">>, true}
                     ]},
-                revs = [<<"1-m">>]
+                revs = [<<"1-m">>, <<"1-l">>]
             }
         ],
         Rows
     ).
 
-t_style_all_docs_with_revtree({DbName, _}) ->
-    ChArgs = #changes_args{style = all_docs},
+t_style_main_only_with_include_docs_conflicts_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        include_docs = true,
+        conflicts = true
+    },
     Req = {json_req, null},
     {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
     ?assertEqual(2, length(Rows)),
-    ?assertEqual(4, UpSeq),
     ?assertEqual(4, LastSeq),
-    ?assertMatch(
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
         [
             #row{
                 seq = 3,
                 id = <<"doc1">>,
-                revs = [<<"2-y">>, <<"2-x">>]
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>]
             },
             #row{
                 seq = 4,
                 id = <<"doc2">>,
                 deleted = true,
-                revs = [<<"1-m">>, <<"1-l">>]
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc2">>},
+                        {<<"_rev">>, <<"1-m">>},
+                        {<<"_deleted">>, true}
+                    ]},
+                revs = [<<"1-m">>]
             }
         ],
         Rows
     ).
 
-t_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
+t_style_all_docs_with_include_docs_conflicts_with_revtree({DbName, _}) ->
     ChArgs = #changes_args{
         style = all_docs,
         include_docs = true,
@@ -824,7 +873,7 @@ t_style_all_docs_with_include_docs_with_revtree({DbName, 
_}) ->
     ?assertEqual(2, length(Rows)),
     ?assertEqual(4, LastSeq),
     ?assertEqual(4, UpSeq),
-    ?assertMatch(
+    ?assertEqual(
         [
             #row{
                 seq = 3,
@@ -835,7 +884,7 @@ t_style_all_docs_with_include_docs_with_revtree({DbName, 
_}) ->
                         {<<"_rev">>, <<"2-y">>},
                         {<<"_conflicts">>, [<<"2-x">>]}
                     ]},
-                revs = [<<"2-y">>, <<"2-x">>]
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
             },
             #row{
                 seq = 4,
@@ -853,6 +902,653 @@ t_style_all_docs_with_include_docs_with_revtree({DbName, 
_}) ->
         Rows
     ).
 
+t_doc_ids_style_main_only_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_doc_ids"
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_doc_ids_style_all_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_doc_ids"
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_doc_ids_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_doc_ids",
+        include_docs = true
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_doc_ids_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_doc_ids",
+        include_docs = true
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_doc_ids_style_main_only_with_include_docs_conflicts_with_revtree({DbName, 
_}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_doc_ids",
+        include_docs = true,
+        conflicts = true
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_doc_ids_style_all_docs_with_include_docs_conflicts_with_revtree({DbName, _}) 
->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_doc_ids",
+        include_docs = true,
+        conflicts = true
+    },
+    DocIds = [<<"doc1">>, <<"doc3">>, <<"doc9999">>],
+    Req = {json_req, {[{<<"doc_ids">>, DocIds}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_main_only_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_selector"
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc = nil,
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_all_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_selector"
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_selector",
+        include_docs = true
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_selector",
+        include_docs = true
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_main_only_with_include_docs_conflicts_with_revtree({DbName, 
_}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_selector",
+        include_docs = true,
+        conflicts = true
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_selector_style_all_docs_with_include_docs_conflicts_with_revtree({DbName, 
_}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_selector",
+        include_docs = true,
+        conflicts = true
+    },
+    LteDoc1 = {[{<<"$lte">>, <<"doc1">>}]},
+    Selector = {[{<<"_id">>, LteDoc1}]},
+    Req = {json_req, {[{<<"selector">>, Selector}]}},
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(4, UpSeq),
+    ?assertEqual(4, LastSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_main_only_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_view"
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_all_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_view"
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_view",
+        include_docs = true
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_view",
+        include_docs = true
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_main_only_with_include_docs_conflicts_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "_view",
+        include_docs = true,
+        conflicts = true
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_view_style_all_docs_with_include_docs_conflicts_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "_view",
+        include_docs = true,
+        conflicts = true
+    },
+    Req = {json_req, {[{<<"query">>, {[{<<"view">>, <<"app/valid">>}]}}]}},
+    ok = update_ddoc(DbName, ?ViewDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_main_only_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "app/valid"
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_all_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "app/valid"
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_main_only_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "app/valid",
+        include_docs = true
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_all_docs_with_include_docs_with_revtree({DbName, _}) ->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "app/valid",
+        include_docs = true
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_main_only_with_include_docs_conflicts_with_revtree({DbName, _}) 
->
+    ChArgs = #changes_args{
+        style = main_only,
+        filter = "app/valid",
+        include_docs = true,
+        conflicts = true
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>]
+            }
+        ],
+        Rows
+    ).
+
+t_custom_style_all_docs_with_include_docs_conflicts_with_revtree({DbName, _}) 
->
+    ChArgs = #changes_args{
+        style = all_docs,
+        filter = "app/valid",
+        include_docs = true,
+        conflicts = true
+    },
+    Req = {json_req, null},
+    ok = update_ddoc(DbName, ?CustomDDoc),
+    {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+    ?assertEqual(1, length(Rows)),
+    ?assertEqual(5, LastSeq),
+    ?assertEqual(5, UpSeq),
+    ?assertEqual(
+        [
+            #row{
+                seq = 3,
+                id = <<"doc1">>,
+                doc =
+                    {[
+                        {<<"_id">>, <<"doc1">>},
+                        {<<"_rev">>, <<"2-y">>},
+                        {<<"_conflicts">>, [<<"2-x">>]}
+                    ]},
+                revs = [<<"2-y">>, <<"2-x">>, <<"2-d">>]
+            }
+        ],
+        Rows
+    ).
+
 %%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
 update_ddoc(DbName, DDoc) ->
     {ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]),
@@ -1110,6 +1806,7 @@ setup_with_revtree() ->
     {ok, Db1} = couch_db:reopen(Db),
     update_replicated(Db1, [
         doc(<<"doc1">>, [<<"y">>, <<"z">>]),
+        doc(<<"doc1">>, [<<"d">>, <<"z">>], true),
         doc(<<"doc2">>, [<<"m">>], true)
     ]),
     ok = couch_db:close(Db1),
diff --git a/src/fabric/src/fabric_rpc.erl b/src/fabric/src/fabric_rpc.erl
index d01f1f5a7..dab69ef4d 100644
--- a/src/fabric/src/fabric_rpc.erl
+++ b/src/fabric/src/fabric_rpc.erl
@@ -546,44 +546,57 @@ changes_enumerator(DocInfo, Acc) ->
         pending = Pending,
         epochs = Epochs
     } = Acc,
-    #doc_info{id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]} 
= DocInfo,
-    case [X || X <- couch_changes:filter(Db, DocInfo, Filter), X /= null] of
-        [] ->
-            ChangesRow =
+    Opts =
+        case Conflicts of
+            true -> [conflicts | DocOptions];
+            false -> DocOptions
+        end,
+    Seq = DocInfo#doc_info.high_seq,
+    {Docs0, Revs} = couch_changes:filter(Db, DocInfo, Filter, IncludeDocs, 
Conflicts),
+    Changes = [X || X <- Revs, X /= null],
+    Docs = [X || X <- Docs0, X /= null],
+    ChangesRow =
+        case {Changes, Docs, IncludeDocs} of
+            {[], _, _} ->
                 {no_pass, [
                     {pending, Pending - 1},
                     {seq, {Seq, uuid(Db), couch_db:owner_of(Epochs, Seq)}}
                 ]};
-        Results ->
-            Opts =
-                if
-                    Conflicts -> [conflicts | DocOptions];
-                    true -> DocOptions
-                end,
-            ChangesRow =
-                {change, [
-                    {pending, Pending - 1},
-                    {seq, {Seq, uuid(Db), couch_db:owner_of(Epochs, Seq)}},
-                    {id, Id},
-                    {changes, Results},
-                    {deleted, Del}
-                    | if
-                        IncludeDocs -> [doc_member(Db, DocInfo, Opts, Filter)];
-                        true -> []
-                    end
-                ]}
-    end,
+            {_, _, false} ->
+                changes_row(Changes, [], DocInfo, Acc);
+            {_, [], true} ->
+                Docs1 = [doc_member(Db, DocInfo, Opts, Filter)],
+                changes_row(Changes, Docs1, DocInfo, Acc);
+            {_, [Doc | _], true} ->
+                Docs1 = [get_json_doc(Doc, Opts, Filter)],
+                changes_row(Changes, Docs1, DocInfo, Acc)
+        end,
     ok = rexi:stream2(ChangesRow),
     {ok, Acc#fabric_changes_acc{seq = Seq, pending = Pending - 1}}.
 
+changes_row(Changes, Docs, DocInfo, Acc) ->
+    #fabric_changes_acc{db = Db, pending = Pending, epochs = Epochs} = Acc,
+    #doc_info{id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]} 
= DocInfo,
+    {change, [
+        {pending, Pending - 1},
+        {seq, {Seq, uuid(Db), couch_db:owner_of(Epochs, Seq)}},
+        {id, Id},
+        {changes, Changes},
+        {deleted, Del}
+        | Docs
+    ]}.
+
 doc_member(Shard, DocInfo, Opts, Filter) ->
     case couch_db:open_doc(Shard, DocInfo, [deleted | Opts]) of
         {ok, Doc} ->
-            {doc, maybe_filtered_json_doc(Doc, Opts, Filter)};
+            get_json_doc(Doc, Opts, Filter);
         Error ->
             Error
     end.
 
+get_json_doc(Doc, Opts, Filter) ->
+    {doc, maybe_filtered_json_doc(Doc, Opts, Filter)}.
+
 maybe_filtered_json_doc(Doc, Opts, {selector, _Style, {_Selector, Fields}}) 
when
     Fields =/= nil
 ->
diff --git a/src/fabric/test/eunit/fabric_changes_test.erl 
b/src/fabric/test/eunit/fabric_changes_test.erl
new file mode 100644
index 000000000..9b850696d
--- /dev/null
+++ b/src/fabric/test/eunit/fabric_changes_test.erl
@@ -0,0 +1,558 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(fabric_changes_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(View,
+    {<<"views">>,
+        {[{<<"v">>, {[{<<"map">>, <<"function(doc) { if (doc._id == 'a') { 
emit(doc); } }">>}]}}]}}
+).
+-define(Custom, {<<"filters">>, {[{<<"f">>, <<"function(doc) { return (doc._id 
== 'a'); }">>}]}}).
+
+fabric_changes_test_() ->
+    {
+        setup,
+        fun setup/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_main_only),
+            ?TDEF(t_all_docs),
+            ?TDEF(t_main_only_include_docs),
+            ?TDEF(t_all_docs_include_docs),
+            ?TDEF(t_main_only_include_docs_conflicts),
+            ?TDEF(t_all_docs_include_docs_conflicts)
+        ])
+    }.
+
+changes_selector_test_() ->
+    {
+        setup,
+        fun setup/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_selector_main_only),
+            ?TDEF(t_selector_all_docs),
+            ?TDEF(t_selector_main_only_include_docs),
+            ?TDEF(t_selector_all_docs_include_docs),
+            ?TDEF(t_selector_main_only_include_docs_conflicts),
+            ?TDEF(t_selector_all_docs_include_docs_conflicts)
+        ])
+    }.
+
+changes_view_test_() ->
+    {
+        setup,
+        fun() -> setup_ddoc(?View) end,
+        fun teardown/1,
+        with([
+            ?TDEF(t_view_main_only),
+            ?TDEF(t_view_all_docs),
+            ?TDEF(t_view_main_only_include_docs),
+            ?TDEF(t_view_all_docs_include_docs),
+            ?TDEF(t_view_main_only_include_docs_conflicts),
+            ?TDEF(t_view_all_docs_include_docs_conflicts)
+        ])
+    }.
+
+changes_custom_test_() ->
+    {
+        setup,
+        fun() -> setup_ddoc(?Custom) end,
+        fun teardown/1,
+        with([
+            ?TDEF(t_custom_main_only),
+            ?TDEF(t_custom_all_docs),
+            ?TDEF(t_custom_main_only_include_docs),
+            ?TDEF(t_custom_all_docs_include_docs),
+            ?TDEF(t_custom_main_only_include_docs_conflicts),
+            ?TDEF(t_custom_all_docs_include_docs_conflicts)
+        ])
+    }.
+
+t_main_only({_, DbName}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{style = main_only}),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes).
+
+t_all_docs({_, DbName}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{style = all_docs}),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ).
+
+t_main_only_include_docs({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            include_docs = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_all_docs_include_docs({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            include_docs = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_main_only_include_docs_conflicts({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_all_docs_include_docs_conflicts({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_selector_main_only({_, DbName}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_selector",
+            filter_fun = {selector, main_only, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}}
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes).
+
+t_selector_all_docs({_, DbName}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_selector",
+            filter_fun = {selector, all_docs, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}}
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ).
+
+t_selector_main_only_include_docs({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_selector",
+            filter_fun = {selector, main_only, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}},
+            include_docs = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_selector_all_docs_include_docs({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_selector",
+            filter_fun = {selector, all_docs, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}},
+            include_docs = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_selector_main_only_include_docs_conflicts({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_selector",
+            filter_fun = {selector, main_only, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_selector_all_docs_include_docs_conflicts({_, DbName}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_selector",
+            filter_fun = {selector, all_docs, {{[{<<"_id">>, {[{<<"$eq">>, 
<<"a">>}]}}]}, nil}},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_view_main_only({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_view",
+            filter_fun = {fetch, view, main_only, {<<"_design/ddoc">>, Rev}, 
<<"v">>}
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes).
+
+t_view_all_docs({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_view",
+            filter_fun = {fetch, view, all_docs, {<<"_design/ddoc">>, Rev}, 
<<"v">>}
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ).
+
+t_view_main_only_include_docs({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_view",
+            filter_fun = {fetch, view, main_only, {<<"_design/ddoc">>, Rev}, 
<<"v">>},
+            include_docs = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_view_all_docs_include_docs({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_view",
+            filter_fun = {fetch, view, all_docs, {<<"_design/ddoc">>, Rev}, 
<<"v">>},
+            include_docs = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_view_main_only_include_docs_conflicts({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "_view",
+            filter_fun = {fetch, view, main_only, {<<"_design/ddoc">>, Rev}, 
<<"v">>},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_view_all_docs_include_docs_conflicts({_, DbName, Rev}) ->
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "_view",
+            filter_fun = {fetch, view, all_docs, {<<"_design/ddoc">>, Rev}, 
<<"v">>},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_custom_main_only({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, main_only, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>}
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes).
+
+t_custom_all_docs({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, all_docs, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>}
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ).
+
+t_custom_main_only_include_docs({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, main_only, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>},
+            include_docs = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_custom_all_docs_include_docs({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, all_docs, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>},
+            include_docs = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>}
+        ]},
+        Doc
+    ).
+
+t_custom_main_only_include_docs_conflicts({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = main_only,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, main_only, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual([{[{<<"rev">>, <<"2-y">>}]}], Changes),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+t_custom_all_docs_include_docs_conflicts({_, DbName, Rev}) ->
+    Req = {json_req, null},
+    {ok, [#{changes := Changes, doc := Doc}], _, _} =
+        changes(DbName, #changes_args{
+            style = all_docs,
+            filter = "ddoc/f",
+            filter_fun = {fetch, custom, all_docs, Req, {<<"_design/ddoc">>, 
Rev}, <<"f">>},
+            include_docs = true,
+            conflicts = true
+        }),
+    ?assertEqual(
+        [
+            {[{<<"rev">>, <<"2-y">>}]},
+            {[{<<"rev">>, <<"2-x">>}]},
+            {[{<<"rev">>, <<"2-d">>}]}
+        ],
+        Changes
+    ),
+    ?assertEqual(
+        {[
+            {<<"_id">>, <<"a">>},
+            {<<"_rev">>, <<"2-y">>},
+            {<<"_conflicts">>, [<<"2-x">>]}
+        ]},
+        Doc
+    ).
+
+%%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
+setup() ->
+    Ctx = test_util:start_couch([fabric]),
+    DbName = ?tempdb(),
+    ok = fabric:create_db(DbName, [{q, 1}, {n, 1}]),
+    Docs = [
+        #doc{id = <<"a">>, revs = {1, [<<"z">>]}},
+        #doc{id = <<"a">>, revs = {2, [<<"x">>, <<"z">>]}},
+        #doc{id = <<"a">>, revs = {2, [<<"y">>, <<"z">>]}},
+        #doc{id = <<"a">>, revs = {2, [<<"d">>, <<"z">>]}, deleted = true}
+    ],
+    Opts = [?REPLICATED_CHANGES],
+    {ok, []} = fabric:update_docs(DbName, Docs, Opts),
+    {Ctx, DbName}.
+
+setup_ddoc(Ddoc) ->
+    {Ctx, DbName} = setup(),
+    Doc = #doc{
+        id = <<"_design/ddoc">>,
+        revs = {0, []},
+        body = {[{<<"language">>, <<"javascript">>}, Ddoc]}
+    },
+    {ok, Rev} = fabric:update_doc(DbName, Doc, [?ADMIN_CTX]),
+    {Ctx, DbName, Rev}.
+
+teardown({Ctx, DbName, _}) ->
+    teardown({Ctx, DbName});
+teardown({Ctx, DbName}) ->
+    ok = fabric:delete_db(DbName, [?ADMIN_CTX]),
+    test_util:stop_couch(Ctx).
+
+changes_callback(start, Acc) ->
+    {ok, Acc};
+changes_callback({change, {Change}}, Acc) ->
+    CM = maps:from_list(Change),
+    {ok, [CM | Acc]};
+changes_callback({stop, EndSeq, Pending}, Acc) ->
+    {ok, Acc, EndSeq, Pending}.
+
+changes(DbName, #changes_args{} = Args) ->
+    fabric_util:isolate(fun() -> fabric:changes(DbName, fun 
changes_callback/2, [], Args) end).

Reply via email to