This is an automated email from the ASF dual-hosted git repository. davisp pushed a commit to branch COUCHDB-3326-clustered-purge-davisp-refactor-2 in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 4782ab5caecd229a0fc2443b8b87ac981d2458f9 Author: Paul J. Davis <paul.joseph.da...@gmail.com> AuthorDate: Tue Apr 24 12:26:01 2018 -0500 Update the purge eunit test suites --- src/couch/src/test_engine_compaction.erl | 197 +++++++++- src/couch/src/test_engine_fold_purge_infos.erl | 133 +++++++ src/couch/src/test_engine_get_set_props.erl | 2 + src/couch/src/test_engine_purge_docs.erl | 40 +- src/couch/src/test_engine_util.erl | 19 +- src/couch/test/couch_db_purge_docs_tests.erl | 497 ++++++++++++++++++++++++ src/couch/test/couch_db_purge_seqs_tests.erl | 217 +++++++++++ src/couch/test/couch_db_purge_upgrade_tests.erl | 74 ++++ 8 files changed, 1158 insertions(+), 21 deletions(-) diff --git a/src/couch/src/test_engine_compaction.erl b/src/couch/src/test_engine_compaction.erl index 44c5357..e49167a 100644 --- a/src/couch/src/test_engine_compaction.erl +++ b/src/couch/src/test_engine_compaction.erl @@ -93,10 +93,8 @@ cet_compact_with_everything() -> BarRev = test_engine_util:prev_rev(BarFDI), Actions3 = [ - {batch, [ - {purge, {<<"foo">>, FooRev#rev_info.rev}}, - {purge, {<<"bar">>, BarRev#rev_info.rev}} - ]} + {purge, {<<"foo">>, FooRev#rev_info.rev}}, + {purge, {<<"bar">>, BarRev#rev_info.rev}} ], {ok, Db4} = test_engine_util:apply_actions(Db3, Actions3), @@ -106,10 +104,9 @@ cet_compact_with_everything() -> {<<"foo">>, [FooRev#rev_info.rev]} ], - ?assertEqual( - PurgedIdRevs, - lists:sort(couch_db_engine:get_last_purged(Db4)) - ), + {ok, PIdRevs4} = couch_db_enigne:fold_purge_infos( + Db4, 0, fun fold_fun/2, [], []), + ?assertEqual(PurgedIdRevs, PIdRevs4), {ok, Db5} = try [Att0, Att1, Att2, Att3, Att4] = test_engine_util:prep_atts(Db4, [ @@ -179,6 +176,186 @@ cet_recompact_updates() -> ?assertEqual(nodiff, Diff). +cet_purge_during_compact() -> + {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath), + + Actions1 = [ + {create, {<<"foo">>, []}}, + {create, {<<"bar">>, []}}, + {conflict, {<<"bar">>, [{<<"vsn">>, 2}]}}, + {create, {<<"baz">>, []}} + ], + + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1), + {ok, St3, DbName, _, Term} = test_engine_util:compact(Engine, St2, Path), + + [BarFDI, BazFDI] = Engine:open_docs(St3, [<<"bar">>, <<"baz">>]), + BarRev = test_engine_util:prev_rev(BarFDI), + BazRev = test_engine_util:prev_rev(BazFDI), + Actions2 = [ + {purge, {<<"bar">>, BarRev#rev_info.rev}}, + {purge, {<<"baz">>, BazRev#rev_info.rev}} + ], + {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2), + Db1 = test_engine_util:db_as_term(Engine, St4), + + {ok, St5, NewPid} = Engine:finish_compaction(St4, DbName, [], Term), + + ?assertEqual(true, is_pid(NewPid)), + Ref = erlang:monitor(process, NewPid), + + NewTerm = receive + {'$gen_cast', {compact_done, Engine, Term0}} -> + Term0; + {'DOWN', Ref, _, _, Reason} -> + erlang:error({compactor_died, Reason}) + after 10000 -> + erlang:error(compactor_timed_out) + end, + + {ok, St6, undefined} = Engine:finish_compaction(St5, DbName, [], NewTerm), + Db2 = test_engine_util:db_as_term(Engine, St6), + Diff = test_engine_util:term_diff(Db1, Db2), + ?assertEqual(nodiff, Diff). + + +cet_multiple_purge_during_compact() -> + {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath), + + Actions1 = [ + {create, {<<"foo">>, []}}, + {create, {<<"bar">>, []}}, + {conflict, {<<"bar">>, [{<<"vsn">>, 2}]}}, + {create, {<<"baz">>, []}} + ], + + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1), + {ok, St3, DbName, _, Term} = test_engine_util:compact(Engine, St2, Path), + + [BarFDI, BazFDI] = Engine:open_docs(St3, [<<"bar">>, <<"baz">>]), + BarRev = test_engine_util:prev_rev(BarFDI), + Actions2 = [ + {purge, {<<"bar">>, BarRev#rev_info.rev}} + ], + {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2), + + BazRev = test_engine_util:prev_rev(BazFDI), + Actions3 = [ + {purge, {<<"baz">>, BazRev#rev_info.rev}} + ], + {ok, St5} = test_engine_util:apply_actions(Engine, St4, Actions3), + + Db1 = test_engine_util:db_as_term(Engine, St5), + {ok, St6, NewPid} = Engine:finish_compaction(St5, DbName, [], Term), + + ?assertEqual(true, is_pid(NewPid)), + Ref = erlang:monitor(process, NewPid), + + NewTerm = receive + {'$gen_cast', {compact_done, Engine, Term0}} -> + Term0; + {'DOWN', Ref, _, _, Reason} -> + erlang:error({compactor_died, Reason}) + after 10000 -> + erlang:error(compactor_timed_out) + end, + + {ok, St7, undefined} = Engine:finish_compaction(St6, DbName, [], NewTerm), + Db2 = test_engine_util:db_as_term(Engine, St7), + Diff = test_engine_util:term_diff(Db1, Db2), + ?assertEqual(nodiff, Diff). + + +cet_recompact_purge() -> + {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath), + + Actions1 = [ + {create, {<<"foo">>, []}}, + {create, {<<"bar">>, []}}, + {conflict, {<<"bar">>, [{<<"vsn">>, 2}]}}, + {create, {<<"baz">>, []}} + ], + + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1), + {ok, St3, DbName, _, Term} = test_engine_util:compact(Engine, St2, Path), + + [BarFDI, BazFDI] = Engine:open_docs(St3, [<<"bar">>, <<"baz">>]), + BarRev = test_engine_util:prev_rev(BarFDI), + BazRev = test_engine_util:prev_rev(BazFDI), + Actions2 = [ + {purge, {<<"bar">>, BarRev#rev_info.rev}}, + {purge, {<<"baz">>, BazRev#rev_info.rev}} + ], + {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2), + Db1 = test_engine_util:db_as_term(Engine, St4), + + {ok, St5, NewPid} = Engine:finish_compaction(St4, DbName, [], Term), + + ?assertEqual(true, is_pid(NewPid)), + Ref = erlang:monitor(process, NewPid), + + NewTerm = receive + {'$gen_cast', {compact_done, Engine, Term0}} -> + Term0; + {'DOWN', Ref, _, _, Reason} -> + erlang:error({compactor_died, Reason}) + after 10000 -> + erlang:error(compactor_timed_out) + end, + + {ok, St6, undefined} = Engine:finish_compaction(St5, DbName, [], NewTerm), + Db2 = test_engine_util:db_as_term(Engine, St6), + Diff = test_engine_util:term_diff(Db1, Db2), + ?assertEqual(nodiff, Diff). + + +% temporary ignoring this test as it times out +ignore_cet_compact_purged_docs_limit() -> + {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath), + % create NumDocs docs + NumDocs = 1200, + {RActions, RIds} = lists:foldl(fun(Id, {CActions, CIds}) -> + Id1 = docid(Id), + Action = {create, {Id1, [{<<"int">>, Id}]}}, + {[Action| CActions], [Id1| CIds]} + end, {[], []}, lists:seq(1, NumDocs)), + Ids = lists:reverse(RIds), + {ok, St2} = test_engine_util:apply_actions(Engine, St1, + lists:reverse(RActions)), + + % purge NumDocs docs + FDIs = Engine:open_docs(St2, Ids), + RevActions2 = lists:foldl(fun(FDI, CActions) -> + Id = FDI#full_doc_info.id, + PrevRev = test_engine_util:prev_rev(FDI), + Rev = PrevRev#rev_info.rev, + [{purge, {Id, Rev}}| CActions] + end, [], FDIs), + {ok, St3} = test_engine_util:apply_actions(Engine, St2, + lists:reverse(RevActions2)), + + % check that before compaction all NumDocs of purge_requests + % are in purge_tree, + % even if NumDocs=1200 is greater than purged_docs_limit=1000 + {ok, PurgedIdRevs} = Engine:fold_purge_infos(St3, 0, fun fold_fun/2, [], []), + ?assertEqual(1, Engine:get_oldest_purge_seq(St3)), + ?assertEqual(NumDocs, length(PurgedIdRevs)), + + % compact db + {ok, St4, DbName, _, Term} = test_engine_util:compact(Engine, St3, Path), + {ok, St5, undefined} = Engine:finish_compaction(St4, DbName, [], Term), + + % check that after compaction only purged_docs_limit purge_requests + % are in purge_tree + PurgedDocsLimit = Engine:get_purge_infos_limit(St5), + OldestPSeq = Engine:get_oldest_purge_seq(St5), + {ok, PurgedIdRevs2} = Engine:fold_purge_infos( + St5, OldestPSeq - 1, fun fold_fun/2, [], []), + ExpectedOldestPSeq = NumDocs - PurgedDocsLimit + 1, + ?assertEqual(ExpectedOldestPSeq, OldestPSeq), + ?assertEqual(PurgedDocsLimit, length(PurgedIdRevs2)). + + docid(I) -> Str = io_lib:format("~4..0b", [I]), iolist_to_binary(Str). @@ -187,3 +364,7 @@ docid(I) -> local_docid(I) -> Str = io_lib:format("_local/~4..0b", [I]), iolist_to_binary(Str). + + +fold_fun({_PSeq, _UUID, Id, Revs}, Acc) -> + {ok, [{Id, Revs} | Acc]}. diff --git a/src/couch/src/test_engine_fold_purge_infos.erl b/src/couch/src/test_engine_fold_purge_infos.erl new file mode 100644 index 0000000..74556c2 --- /dev/null +++ b/src/couch/src/test_engine_fold_purge_infos.erl @@ -0,0 +1,133 @@ +% 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(test_engine_fold_purge_infos). +-compile(export_all). + + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +-define(NUM_DOCS, 100). + + +cet_empty_purged_docs() -> + {ok, Engine, St} = test_engine_util:init_engine(), + ?assertEqual({ok, []}, Engine:fold_purge_infos(St, 0, fun fold_fun/2, [], [])). + + +cet_all_purged_docs() -> + {ok, Engine, St1} = test_engine_util:init_engine(), + + {RActions, RIds} = lists:foldl(fun(Id, {CActions, CIds}) -> + Id1 = docid(Id), + Action = {create, {Id1, [{<<"int">>, Id}]}}, + {[Action| CActions], [Id1| CIds]} + end, {[], []}, lists:seq(1, ?NUM_DOCS)), + Actions = lists:reverse(RActions), + Ids = lists:reverse(RIds), + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions), + + FDIs = Engine:open_docs(St2, Ids), + {RevActions2, RevIdRevs} = lists:foldl(fun(FDI, {CActions, CIdRevs}) -> + Id = FDI#full_doc_info.id, + PrevRev = test_engine_util:prev_rev(FDI), + Rev = PrevRev#rev_info.rev, + Action = {purge, {Id, Rev}}, + {[Action| CActions], [{Id, [Rev]}| CIdRevs]} + end, {[], []}, FDIs), + {Actions2, IdsRevs} = {lists:reverse(RevActions2), lists:reverse(RevIdRevs)}, + + {ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2), + {ok, PurgedIdRevs} = Engine:fold_purge_infos(St3, 0, fun fold_fun/2, [], []), + ?assertEqual(IdsRevs, lists:reverse(PurgedIdRevs)). + + +cet_start_seq() -> + {ok, Engine, St1} = test_engine_util:init_engine(), + Actions1 = [ + {create, {docid(1), [{<<"int">>, 1}]}}, + {create, {docid(2), [{<<"int">>, 2}]}}, + {create, {docid(3), [{<<"int">>, 3}]}}, + {create, {docid(4), [{<<"int">>, 4}]}}, + {create, {docid(5), [{<<"int">>, 5}]}} + ], + Ids = [docid(1), docid(2), docid(3), docid(4), docid(5)], + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1), + + FDIs = Engine:open_docs(St2, Ids), + {RActions2, RIdRevs} = lists:foldl(fun(FDI, {CActions, CIdRevs}) -> + Id = FDI#full_doc_info.id, + PrevRev = test_engine_util:prev_rev(FDI), + Rev = PrevRev#rev_info.rev, + Action = {purge, {Id, Rev}}, + {[Action| CActions], [{Id, [Rev]}| CIdRevs]} + end, {[], []}, FDIs), + {ok, St3} = test_engine_util:apply_actions(Engine, St2, lists:reverse(RActions2)), + + StartSeq = 3, + StartSeqIdRevs = lists:nthtail(StartSeq, lists:reverse(RIdRevs)), + {ok, PurgedIdRevs} = Engine:fold_purge_infos(St3, StartSeq, fun fold_fun/2, [], []), + ?assertEqual(StartSeqIdRevs, lists:reverse(PurgedIdRevs)). + + +cet_id_rev_repeated() -> + {ok, Engine, St1} = test_engine_util:init_engine(), + + Actions1 = [ + {create, {<<"foo">>, [{<<"vsn">>, 1}]}}, + {conflict, {<<"foo">>, [{<<"vsn">>, 2}]}} + ], + {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1), + + [FDI1] = Engine:open_docs(St2, [<<"foo">>]), + PrevRev1 = test_engine_util:prev_rev(FDI1), + Rev1 = PrevRev1#rev_info.rev, + Actions2 = [ + {purge, {<<"foo">>, Rev1}} + ], + {ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2), + PurgedIdRevs0 = [{<<"foo">>, [Rev1]}], + {ok, PurgedIdRevs1} = Engine:fold_purge_infos(St3, 0, fun fold_fun/2, [], []), + ?assertEqual(PurgedIdRevs0, PurgedIdRevs1), + ?assertEqual(1, Engine:get_purge_seq(St3)), + + % purge the same Id,Rev when the doc still exists + {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2), + {ok, PurgedIdRevs2} = Engine:fold_purge_infos(St4, 0, fun fold_fun/2, [], []), + ?assertEqual(PurgedIdRevs0, PurgedIdRevs2), + ?assertEqual(1, Engine:get_purge_seq(St4)), + + [FDI2] = Engine:open_docs(St4, [<<"foo">>]), + PrevRev2 = test_engine_util:prev_rev(FDI2), + Rev2 = PrevRev2#rev_info.rev, + Actions3 = [ + {purge, {<<"foo">>, Rev2}} + ], + {ok, St5} = test_engine_util:apply_actions(Engine, St4, Actions3), + PurgedIdRevs00 = [{<<"foo">>, [Rev1]}, {<<"foo">>, [Rev2]}], + + % purge the same Id,Rev when the doc was completely purged + {ok, St6} = test_engine_util:apply_actions(Engine, St5, Actions3), + {ok, PurgedIdRevs3} = Engine:fold_purge_infos(St6, 0, fun fold_fun/2, [], []), + ?assertEqual(PurgedIdRevs00, lists:reverse(PurgedIdRevs3)), + ?assertEqual(2, Engine:get_purge_seq(St6)). + + +fold_fun({_PSeq, _UUID, Id, Revs}, Acc) -> + {ok, [{Id, Revs} | Acc]}. + + +docid(I) -> + Str = io_lib:format("~4..0b", [I]), + iolist_to_binary(Str). diff --git a/src/couch/src/test_engine_get_set_props.erl b/src/couch/src/test_engine_get_set_props.erl index 764fe39..5cbca7f 100644 --- a/src/couch/src/test_engine_get_set_props.erl +++ b/src/couch/src/test_engine_get_set_props.erl @@ -29,6 +29,8 @@ cet_default_props() -> ?assertEqual(true, is_integer(couch_db_engine:get_disk_version(Db))), ?assertEqual(0, couch_db_engine:get_update_seq(Db)), ?assertEqual(0, couch_db_engine:get_purge_seq(Db)), + ?assertEqual(true, is_integer(couch_db_engine:get_purge_infos_limit(Db))), + ?assertEqual(true, couch_db_engine:get_purge_infos_limit(Db) > 0), ?assertEqual([], couch_db_engine:get_last_purged(Db)), ?assertEqual([], couch_db_engine:get_security(Db)), ?assertEqual(1000, couch_db_engine:get_revs_limit(Db)), diff --git a/src/couch/src/test_engine_purge_docs.erl b/src/couch/src/test_engine_purge_docs.erl index 7d83f60..a7fd901 100644 --- a/src/couch/src/test_engine_purge_docs.erl +++ b/src/couch/src/test_engine_purge_docs.erl @@ -25,12 +25,14 @@ cet_purge_simple() -> {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}} ], {ok, Db2} = test_engine_util:apply_actions(Db1, Actions1), + {ok, PIdRevs2} = couch_db_engine:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), ?assertEqual(1, couch_db_engine:get_doc_count(Db2)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), ?assertEqual(1, couch_db_engine:get_update_seq(Db2)), ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), - ?assertEqual([], couch_db_engine:get_last_purged(Db2)), + ?assertEqual([], PIdRevs2), [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]), PrevRev = test_engine_util:prev_rev(FDI), @@ -40,12 +42,14 @@ cet_purge_simple() -> {purge, {<<"foo">>, Rev}} ], {ok, Db3} = test_engine_util:apply_actions(Db2, Actions2), + {ok, PIdRevs3} = couch_db_engine:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), ?assertEqual(0, couch_db_engine:get_doc_count(Db3)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), ?assertEqual(2, couch_db_engine:get_update_seq(Db3)), ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), - ?assertEqual([{<<"foo">>, [Rev]}], couch_db_engine:get_last_purged(Db3)). + ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3). cet_purge_conflicts() -> @@ -56,12 +60,14 @@ cet_purge_conflicts() -> {conflict, {<<"foo">>, {[{<<"vsn">>, 2}]}}} ], {ok, Db2} = test_engine_util:apply_actions(Db1, Actions1), + {ok, PIdRevs2} = couch_db_engine:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), ?assertEqual(1, couch_db_engine:get_doc_count(Db2)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), - ?assertEqual([], couch_db_engine:get_last_purged(Db2)), + ?assertEqual([], PIdRevs2), [FDI1] = couch_db_engine:open_docs(Db2, [<<"foo">>]), PrevRev1 = test_engine_util:prev_rev(FDI1), @@ -71,12 +77,14 @@ cet_purge_conflicts() -> {purge, {<<"foo">>, Rev1}} ], {ok, Db3} = test_engine_util:apply_actions(Db2, Actions2), + {ok, PIdRevs3} = couch_db_engine:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), ?assertEqual(1, couch_db_engine:get_doc_count(Db3)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), ?assertEqual(4, couch_db_engine:get_update_seq(Db3)), ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), - ?assertEqual([{<<"foo">>, [Rev1]}], couch_db_engine:get_last_purged(Db3)), + ?assertEqual([{<<"foo">>, [Rev1]}], PIdRevs3), [FDI2] = couch_db_engine:open_docs(Db3, [<<"foo">>]), PrevRev2 = test_engine_util:prev_rev(FDI2), @@ -86,12 +94,14 @@ cet_purge_conflicts() -> {purge, {<<"foo">>, Rev2}} ], {ok, Db4} = test_engine_util:apply_actions(Db3, Actions3), + {ok, PIdRevs4} = couch_db_engine:fold_purge_infos( + Db4, 0, fun fold_fun/2, [], []), ?assertEqual(0, couch_db_engine:get_doc_count(Db4)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db4)), ?assertEqual(5, couch_db_engine:get_update_seq(Db4)), ?assertEqual(2, couch_db_engine:get_purge_seq(Db4)), - ?assertEqual([{<<"foo">>, [Rev2]}], couch_db_engine:get_last_purged(Db4)). + ?assertEqual([{<<"foo">>, [Rev2]}, {<<"foo">>, [Rev1]}], PIdRevs4). cet_add_delete_purge() -> @@ -103,12 +113,14 @@ cet_add_delete_purge() -> ], {ok, Db2} = test_engine_util:apply_actions(Db1, Actions1), + {ok, PIdRevs2} = couch_db_engine:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), ?assertEqual(0, couch_db_engine:get_doc_count(Db2)), ?assertEqual(1, couch_db_engine:get_del_doc_count(Db2)), ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), - ?assertEqual([], couch_db_engine:get_last_purged(Db2)), + ?assertEqual([], PIdRevs2), [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]), PrevRev = test_engine_util:prev_rev(FDI), @@ -118,12 +130,14 @@ cet_add_delete_purge() -> {purge, {<<"foo">>, Rev}} ], {ok, Db3} = test_engine_util:apply_actions(Db2, Actions2), + {ok, PIdRevs3} = couch_db_engine:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), ?assertEqual(0, couch_db_engine:get_doc_count(Db3)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), - ?assertEqual([{<<"foo">>, [Rev]}], couch_db_engine:get_last_purged(Db3)). + ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3). cet_add_two_purge_one() -> @@ -135,12 +149,14 @@ cet_add_two_purge_one() -> ], {ok, Db2} = test_engine_util:apply_actions(Db1, Actions1), + {ok, PIdRevs2} = couch_db_engine:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), - ?assertEqual([], couch_db_engine:get_last_purged(Db2)), + ?assertEqual([], PIdRevs2), [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]), PrevRev = test_engine_util:prev_rev(FDI), @@ -150,9 +166,15 @@ cet_add_two_purge_one() -> {purge, {<<"foo">>, Rev}} ], {ok, Db3} = test_engine_util:apply_actions(Db2, Actions2), + {ok, PIdRevs3} = couch_db_engine:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), ?assertEqual(1, couch_db_engine:get_doc_count(Db3)), ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), - ?assertEqual([{<<"foo">>, [Rev]}], couch_db_engine:get_last_purged(Db3)). + ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3). + + +fold_fun({_Pseq, _UUID, Id, Revs}, Acc) -> + {ok, [{Id, Revs} | Acc]}. diff --git a/src/couch/src/test_engine_util.erl b/src/couch/src/test_engine_util.erl index fbb4a8e..9eb6dd0 100644 --- a/src/couch/src/test_engine_util.erl +++ b/src/couch/src/test_engine_util.erl @@ -24,6 +24,7 @@ test_engine_attachments, test_engine_fold_docs, test_engine_fold_changes, + test_engine_fold_purge_infos, test_engine_purge_docs, test_engine_compaction, test_engine_ref_counting @@ -322,7 +323,8 @@ db_as_term(Db) -> {props, db_props_as_term(Db)}, {docs, db_docs_as_term(Db)}, {local_docs, db_local_docs_as_term(Db)}, - {changes, db_changes_as_term(Db)} + {changes, db_changes_as_term(Db)}, + {purged_docs, db_purged_docs_as_term(Db)} ]. @@ -333,7 +335,7 @@ db_props_as_term(Db) -> get_disk_version, get_update_seq, get_purge_seq, - get_last_purged, + get_purge_infos_limit, get_security, get_revs_limit, get_uuid, @@ -366,6 +368,15 @@ db_changes_as_term(Db) -> end, Changes)). +db_purged_docs_as_term(Db) -> + PSeq = couch_db_engine:get_oldest_purge_seq(Db) - 1, + FoldFun = fun({PSeq, UUID, Id, Revs}, Acc) -> + {ok, [{PSeq, UUID, Id, Revs} | Acc]} + end, + {ok, PDocs} = couch_db_engine:fold_purge_infos(Db, PSeq, FoldFun, [], []), + lists:reverse(PDocs). + + fdi_to_term(Db, FDI) -> #full_doc_info{ id = DocId, @@ -494,8 +505,8 @@ compact(Db) -> ok; {'DOWN', Ref, _, _, Reason} -> erlang:error({compactor_died, Reason}) - after ?COMPACTOR_TIMEOUT -> - erlang:error(compactor_timed_out) + after ?COMPACTOR_TIMEOUT -> + erlang:error(compactor_timed_out) end, test_util:wait(fun() -> diff --git a/src/couch/test/couch_db_purge_docs_tests.erl b/src/couch/test/couch_db_purge_docs_tests.erl new file mode 100644 index 0000000..ed42f2b --- /dev/null +++ b/src/couch/test/couch_db_purge_docs_tests.erl @@ -0,0 +1,497 @@ +% 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(couch_db_purge_docs_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +setup() -> + DbName = ?tempdb(), + {ok, _Db} = create_db(DbName), + DbName. + +teardown(DbName) -> + delete_db(DbName), + ok. + +couch_db_purge_docs_test_() -> + { + "Couch_db purge_docs", + [ + { + setup, + fun test_util:start_couch/0, fun test_util:stop_couch/1, + [couch_db_purge_docs()] + }, + purge_with_replication() + ] + + }. + + +couch_db_purge_docs() -> + { + foreach, + fun setup/0, fun teardown/1, + [ + fun test_purge_all/1, + fun test_purge_some/1, + fun test_purge_none/1, + fun test_purge_missing_docid/1, + fun test_purge_repeated_docid/1, + %fun test_purge_repeated_rev/1, % improving + fun test_purge_partial/1, + fun test_all_removal_purges/1, + fun purge_id_not_exist/1, + fun purge_non_leaf_rev/1, + fun purge_deep_tree/1 + ] + }. + + +test_purge_all(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), UUID2 = couch_uuids:new(), + {ok, [{ok, PRevs}, {ok, PRevs2}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo1">>, [Rev]}, {UUID2, <<"foo2">>, [Rev2]}] + ), + + ?assertEqual([Rev], PRevs), + ?assertEqual([Rev2], PRevs2), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(0, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(2, couch_db_engine:get_purge_seq(Db3)), + ?assertEqual([{<<"foo2">>, [Rev2]}, {<<"foo1">>, [Rev]}], PIdsRevs) + end). + + +test_all_removal_purges(DbName) -> + ?_test( + begin + {ok, Db0} = couch_db:open_int(DbName, []), + Doc0 = {[{<<"_id">>,<<"foo">>}, {<<"vsn">>, 1}]}, + {ok, Rev} = save_doc(Db0, Doc0), + couch_db:ensure_full_commit(Db0), + {ok, Db1} = couch_db:reopen(Db0), + + Doc1 = {[ + {<<"_id">>, <<"foo">>}, {<<"vsn">>, 2}, + {<<"_rev">>, couch_doc:rev_to_str(Rev)}, + {<<"_deleted">>, true}] + }, + {ok, Rev2} = save_doc(Db1, Doc1), + couch_db:ensure_full_commit(Db1), + + {ok, Db2} = couch_db:reopen(Db1), + {ok, PIdsRevs1} = couch_db:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), + ?assertEqual(0, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(1, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + ?assertEqual([], PIdsRevs1), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo">>, [Rev2]}]), + ?assertEqual([Rev2], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, PIdsRevs2} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(0, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), + ?assertEqual([{<<"foo">>, [Rev2]}], PIdsRevs2) + end). + + +test_purge_some(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db2, + [{UUID, <<"foo1">>, [Rev]}]), + ?assertEqual([Rev], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(1, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)), + ?assertEqual([{<<"foo1">>, [Rev]}], PIdsRevs) + end). + + +test_purge_none(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 2}]}, + {ok, _Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + {ok, []} = couch_db:purge_docs(Db2, []), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(2, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db3)), + ?assertEqual([], PIdsRevs) + end). + + +test_purge_missing_docid(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db2, + [{UUID, <<"">>, [Rev]}]), + ?assertEqual([], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, _PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(2, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db3)) + end). + + +test_purge_repeated_docid(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), + UUID2 = couch_uuids:new(), + {ok, [{ok, PRevs}, {ok, PRevs}]} = couch_db:purge_docs(Db2, + [{UUID, <<"foo1">>, [Rev]}, {UUID2, <<"foo1">>, [Rev]}] + ), + ?assertEqual([Rev], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, _PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(1, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(2, couch_db_engine:get_purge_seq(Db3)) + end). + + +purge_id_not_exist(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db, + [{UUID, <<"foo">>, [{0, <<0>>}]}]), + ?assertEqual([], PRevs), + + {ok, Db2} = couch_db:reopen(Db), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db2, 0, fun fold_fun/2, [], []), + ?assertEqual(0, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + ?assertEqual([], PIdsRevs) + end). + + +purge_non_leaf_rev(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc0 = {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]}, + {ok, Rev} = save_doc(Db, Doc0), + couch_db:ensure_full_commit(Db), + {ok, Db2} = couch_db:reopen(Db), + + Doc1 = {[ + {<<"_id">>, <<"foo">>}, {<<"vsn">>, 2}, + {<<"_rev">>, couch_doc:rev_to_str(Rev)} + ]}, + {ok, _Rev2} = save_doc(Db2, Doc1), + couch_db:ensure_full_commit(Db2), + {ok, Db3} = couch_db:reopen(Db2), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db3, + [{UUID, <<"foo">>, [Rev]}]), + ?assertEqual([], PRevs), + + {ok, Db4} = couch_db:reopen(Db3), + {ok, PIdsRevs} = couch_db:fold_purge_infos(Db4, 0, fun fold_fun/2, [], []), + ?assertEqual(1, couch_db_engine:get_doc_count(Db4)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db4)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db4)), + ?assertEqual([], PIdsRevs) + end). + + +test_purge_partial(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc = {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, <<"v1.1">>}]}, + {ok, Rev} = save_doc(Db, Doc), + couch_db:ensure_full_commit(Db), + {ok, Db2} = couch_db:reopen(Db), + + % create a conflict + DocConflict = #doc{ + id = <<"foo">>, + revs = {1, [crypto:hash(md5, <<"v1.2">>)]}, + body = {[ {<<"vsn">>, <<"v1.2">>}]} + }, + {ok, _} = couch_db:update_doc(Db2, DocConflict, [], replicated_changes), + couch_db:ensure_full_commit(Db2), + {ok, Db3} = couch_db:reopen(Db2), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db3, + [{UUID, <<"foo">>, [Rev]}]), + ?assertEqual([Rev], PRevs), + + {ok, Db4} = couch_db:reopen(Db3), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db4, 0, fun fold_fun/2, [], []), + % still has one doc + ?assertEqual(1, couch_db_engine:get_doc_count(Db4)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db4)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db4)), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db4)), + ?assertEqual([{<<"foo">>, [Rev]}], PIdsRevs) + end). + + +test_purge_repeated_rev(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc = {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, <<"v1.1">>}]}, + {ok, Rev} = save_doc(Db, Doc), + couch_db:ensure_full_commit(Db), + {ok, Db2} = couch_db:reopen(Db), + + % create a conflict + DocConflict = #doc{ + id = <<"foo">>, + revs = {1, [crypto:hash(md5, <<"v1.2">>)]}, + body = {[ {<<"vsn">>, <<"v1.2">>}]} + }, + {ok, _} = couch_db:update_doc(Db2, DocConflict, [], replicated_changes), + couch_db:ensure_full_commit(Db2), + {ok, Db3} = couch_db:reopen(Db2), + + UUID = couch_uuids:new(), + UUID2 = couch_uuids:new(), + + {ok, Doc2} = couch_db:get_full_doc_info(Db2, <<"foo">>), + + {ok, [{ok, _PRevs}, {ok, _PRevs2}]} = couch_db:purge_docs(Db2, + [{UUID, <<"foo">>, [Rev]}, {UUID2, <<"foo">>, [Rev]}] + ), + + {ok, Db4} = couch_db:reopen(Db3), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db4, 0, fun fold_fun/2, [], []), + % still has one doc + ?assertEqual(1, couch_db_engine:get_doc_count(Db4)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db4)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db4)), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db4)), + ?assertEqual([{<<"foo">>, [Rev]}], PIdsRevs) + end). + + +purge_deep_tree(DbName) -> + ?_test( + begin + NRevs = 100, + {ok, Db0} = couch_db:open_int(DbName, []), + Doc0 = {[{<<"_id">>, <<"bar">>}, {<<"vsn">>, 0}]}, + {ok, InitRev} = save_doc(Db0, Doc0), + ok = couch_db:close(Db0), + LastRev = lists:foldl(fun(V, PrevRev) -> + {ok, Db} = couch_db:open_int(DbName, []), + {ok, Rev} = save_doc(Db, + {[{<<"_id">>, <<"bar">>}, + {<<"vsn">>, V}, + {<<"_rev">>, couch_doc:rev_to_str(PrevRev)}]} + ), + ok = couch_db:close(Db), + Rev + end, InitRev, lists:seq(2, NRevs)), + {ok, Db1} = couch_db:open_int(DbName, []), + + % purge doc + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db1, + [{UUID, <<"bar">>, [LastRev]}]), + ?assertEqual([LastRev], PRevs), + + {ok, Db2} = couch_db:reopen(Db1), + % no docs left + ?assertEqual(0, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db2)), + ?assertEqual(NRevs + 1 , couch_db_engine:get_update_seq(Db2)) + end). + + +purge_with_replication() -> + ?_test( + begin + Ctx = test_util:start_couch([couch_replicator]), + Source = ?tempdb(), + {ok, SourceDb} = create_db(Source), + Target = ?tempdb(), + {ok, _Db} = create_db(Target), + + % create Doc and do replication to Target + {ok, Rev} = save_doc(SourceDb, + {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]}), + couch_db:ensure_full_commit(SourceDb), + {ok, SourceDb2} = couch_db:reopen(SourceDb), + RepObject = {[ + {<<"source">>, Source}, + {<<"target">>, Target} + ]}, + {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + {ok, TargetDb} = couch_db:open_int(Target, []), + {ok, Doc} = couch_db:get_doc_info(TargetDb, <<"foo">>), + + % purge Doc on Source and do replication to Target + % assert purges don't get replicated to Target + UUID = couch_uuids:new(), + {ok, _} = couch_db:purge_docs(SourceDb2, [{UUID, <<"foo">>, [Rev]}]), + {ok, SourceDb3} = couch_db:reopen(SourceDb2), + {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + {ok, TargetDb2} = couch_db:open_int(Target, []), + {ok, Doc2} = couch_db:get_doc_info(TargetDb2, <<"foo">>), + [Rev2] = Doc2#doc_info.revs, + ?assertEqual(Rev, Rev2#rev_info.rev), + ?assertEqual(Doc, Doc2), + ?assertEqual(0, couch_db_engine:get_doc_count(SourceDb3)), + ?assertEqual(1, couch_db_engine:get_purge_seq(SourceDb3)), + ?assertEqual(1, couch_db_engine:get_doc_count(TargetDb2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(TargetDb2)), + + % replicate from Target to Source + % assert that Doc reappears on Source + RepObject2 = {[ + {<<"source">>, Target}, + {<<"target">>, Source} + ]}, + {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER), + {ok, SourceDb4} = couch_db:reopen(SourceDb3), + {ok, Doc3} = couch_db:get_doc_info(SourceDb4, <<"foo">>), + [Rev3] = Doc3#doc_info.revs, + ?assertEqual(Rev, Rev3#rev_info.rev), + ?assertEqual(1, couch_db_engine:get_doc_count(SourceDb4)), + ?assertEqual(1, couch_db_engine:get_purge_seq(SourceDb4)), + + delete_db(Source), + delete_db(Target), + ok = application:stop(couch_replicator), + ok = test_util:stop_couch(Ctx) + end). + + +create_db(DbName) -> + couch_db:create(DbName, [?ADMIN_CTX, overwrite]). + +delete_db(DbName) -> + couch_server:delete(DbName, [?ADMIN_CTX]). + +save_doc(Db, Json) -> + Doc = couch_doc:from_json_obj(Json), + couch_db:update_doc(Db, Doc, []). + +fold_fun({_PSeq, _UUID, Id, Revs}, Acc) -> + {ok, [{Id, Revs} | Acc]}. \ No newline at end of file diff --git a/src/couch/test/couch_db_purge_seqs_tests.erl b/src/couch/test/couch_db_purge_seqs_tests.erl new file mode 100644 index 0000000..214d5b1 --- /dev/null +++ b/src/couch/test/couch_db_purge_seqs_tests.erl @@ -0,0 +1,217 @@ +% 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(couch_db_purge_seqs_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +setup() -> + DbName = ?tempdb(), + {ok, _Db} = create_db(DbName), + DbName. + +teardown(DbName) -> + delete_db(DbName), + ok. + +couch_db_purge_seqs_test_() -> + { + "Couch_db purge_seqs", + [ + { + setup, + fun test_util:start_couch/0, fun test_util:stop_couch/1, + [couch_db_purge_seqs()] + } + ] + }. + + +couch_db_purge_seqs() -> + { + foreach, + fun setup/0, fun teardown/1, + [ + fun test_update_seq_bounce/1, + fun test_update_seq_inc_on_complete_purge/1, + fun test_purge_seq_bounce/1, + fun test_fold_purge_infos/1, + fun test_purge_seq/1 + ] + }. + +test_update_seq_bounce(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo1">>, [Rev]}] + ), + + ?assertEqual([Rev], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, _PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)) + end). + + +test_update_seq_inc_on_complete_purge(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs( + Db2, [{UUID, <<"invalid">>, [Rev]}] + ), + + {ok, Db3} = couch_db:reopen(Db2), + ?assertEqual(2, couch_db_engine:get_update_seq(Db3)), + + ?assertEqual([], PRevs), + + UUID2 = couch_uuids:new(), + {ok, [{ok, PRevs2}]} = couch_db:purge_docs( + Db3, [{UUID2, <<"foo1">>, [Rev]}] + ), + + ?assertEqual([Rev], PRevs2), + + {ok, Db4} = couch_db:reopen(Db3), + ?assertEqual(3, couch_db_engine:get_update_seq(Db4)) + end). + + +test_purge_seq_bounce(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo1">>, [Rev]}] + ), + + ?assertEqual([Rev], PRevs), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, _PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)) + end). + + +test_fold_purge_infos(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)), + ?assertEqual(2, couch_db_engine:get_update_seq(Db2)), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + UUID = couch_uuids:new(), UUID2 = couch_uuids:new(), + {ok, [{ok, PRevs}, {ok, PRevs2}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo1">>, [Rev]}, {UUID2, <<"foo2">>, [Rev2]}] + ), + + ?assertEqual([Rev], PRevs), + ?assertEqual([Rev2], PRevs2), + + {ok, Db3} = couch_db:reopen(Db2), + {ok, PIdsRevs} = couch_db:fold_purge_infos( + Db3, 0, fun fold_fun/2, [], []), + ?assertEqual(0, couch_db_engine:get_doc_count(Db3)), + ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)), + ?assertEqual(3, couch_db_engine:get_update_seq(Db3)), + ?assertEqual(2, couch_db_engine:get_purge_seq(Db3)), + ?assertEqual([{<<"foo2">>, [Rev2]}, {<<"foo1">>, [Rev]}], PIdsRevs) + end). + + +test_purge_seq(DbName) -> + ?_test( + begin + {ok, Db} = couch_db:open_int(DbName, []), + Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]}, + Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]}, + {ok, Rev} = save_doc(Db, Doc1), + {ok, _Rev2} = save_doc(Db, Doc2), + couch_db:ensure_full_commit(Db), + + {ok, Db2} = couch_db:reopen(Db), + ?assertEqual(2, couch_db_engine:get_doc_count(Db2)), + UUID = couch_uuids:new(), + {ok, [{ok, PRevs}]} = couch_db:purge_docs( + Db2, [{UUID, <<"foo1">>, [Rev]}] + ), + + ?assertEqual([Rev], PRevs), + ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)), + + {ok, Db3} = couch_db:reopen(Db2), + ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)) + end). + + +create_db(DbName) -> + couch_db:create(DbName, [?ADMIN_CTX, overwrite]). + +delete_db(DbName) -> + couch_server:delete(DbName, [?ADMIN_CTX]). + +save_doc(Db, Json) -> + Doc = couch_doc:from_json_obj(Json), + couch_db:update_doc(Db, Doc, []). + +fold_fun({_PSeq, _UUID, Id, Revs}, Acc) -> + {ok, [{Id, Revs} | Acc]}. diff --git a/src/couch/test/couch_db_purge_upgrade_tests.erl b/src/couch/test/couch_db_purge_upgrade_tests.erl new file mode 100644 index 0000000..0149b62 --- /dev/null +++ b/src/couch/test/couch_db_purge_upgrade_tests.erl @@ -0,0 +1,74 @@ +% 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(couch_db_purge_upgrade_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + +-define(CONTENT_JSON, {"Content-Type", "application/json"}). +-define(TIMEOUT, 1000). + + +setup() -> + DbName = <<"db_with_1_purge_req">>, + DbFileName = "db_with_1_purge_req.couch", + OldDbFilePath = filename:join([?FIXTURESDIR, DbFileName]), + DbDir = config:get("couchdb", "database_dir"), + NewDbFilePath = filename:join([DbDir, DbFileName]), + Files = [NewDbFilePath], + + %% make sure there is no left over + lists:foreach(fun(File) -> file:delete(File) end, Files), + file:copy(OldDbFilePath, NewDbFilePath), + {DbName, Files}. + + +teardown({_DbName, Files}) -> + lists:foreach(fun(File) -> file:delete(File) end, Files). + + +purge_upgrade_test_() -> + { + "Purge Upgrade tests", + { + setup, + fun test_util:start_couch/0, fun test_util:stop_couch/1, + { + foreach, + fun setup/0, fun teardown/1, + [ + %fun should_upgrade_legacy_db_with_0_purge_req/1, + %fun should_upgrade_legacy_db_with_1_purge_req/1 + %fun should_upgrade_legacy_db_with_N_purge_req/1 + ] + } + } + }. + + +should_upgrade_legacy_db_with_1_purge_req({DbName, Files}) -> + ?_test(begin + [_NewDbFilePath] = Files, + ok = config:set("query_server_config", "commit_freq", "0", false), + % add doc to trigger update + DocUrl = db_url(DbName) ++ "/boo", + {ok, Status, _Resp, _Body} = test_request:put( + DocUrl, [{"Content-Type", "application/json"}], <<"{\"a\":3}">>), + ?assert(Status =:= 201 orelse Status =:= 202) + end). + + +db_url(DbName) -> + Addr = config:get("httpd", "bind_address", "127.0.0.1"), + Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)), + "http://" ++ Addr ++ ":" ++ Port ++ "/" ++ ?b2l(DbName). -- To stop receiving notification emails like this one, please contact dav...@apache.org.