summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2018-04-24 12:26:01 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2018-04-26 13:58:36 -0500
commit4782ab5caecd229a0fc2443b8b87ac981d2458f9 (patch)
tree51266e60b483267ce889106732940ba9e9414743
parent57a8dc2aa6211a892f4b585ed61ead0763645700 (diff)
downloadcouchdb-4782ab5caecd229a0fc2443b8b87ac981d2458f9.tar.gz
Update the purge eunit test suites
-rw-r--r--src/couch/src/test_engine_compaction.erl197
-rw-r--r--src/couch/src/test_engine_fold_purge_infos.erl133
-rw-r--r--src/couch/src/test_engine_get_set_props.erl2
-rw-r--r--src/couch/src/test_engine_purge_docs.erl40
-rw-r--r--src/couch/src/test_engine_util.erl19
-rw-r--r--src/couch/test/couch_db_purge_docs_tests.erl497
-rw-r--r--src/couch/test/couch_db_purge_seqs_tests.erl217
-rw-r--r--src/couch/test/couch_db_purge_upgrade_tests.erl74
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 44c5357a6..e49167a10 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 000000000..74556c2ca
--- /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 764fe393d..5cbca7f3a 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 7d83f6035..a7fd901c2 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 fbb4a8e69..9eb6dd075 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 000000000..ed42f2bfa
--- /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 000000000..214d5b1db
--- /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 000000000..0149b6213
--- /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).