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).