blob: 58d869f7fdc1a2dd9da7c62bbfd0846868256479 [file] [log] [blame]
% 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 purge_simple/1,
fun add_delete_purge/1,
fun add_two_purge_one/1,
fun purge_id_not_exist/1,
fun purge_non_leaf_rev/1,
fun purge_conflicts/1,
fun purge_deep_tree/1
]
}.
purge_simple(DbName) ->
?_test(
begin
{ok, Db} = couch_db:open_int(DbName, []),
DocId = <<"foo">>,
{ok, Rev} = save_doc(Db, {[{<<"_id">>, DocId}, {<<"vsn">>, 1}]}),
couch_db:ensure_full_commit(Db),
{ok, Db2} = couch_db:reopen(Db),
?assertEqual(1, couch_db_engine:get(Db2, doc_count)),
?assertEqual(0, couch_db_engine:get(Db2, del_doc_count)),
?assertEqual(1, couch_db_engine:get(Db2, update_seq)),
?assertEqual(0, couch_db_engine:get(Db2, purge_seq)),
?assertEqual(nil, couch_db_engine:get(Db2, purge_tree_state)),
UUID = couch_uuids:new(),
{ok, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db2,
[{UUID, DocId, [Rev]}]),
?assertEqual([Rev], PRevs),
?assertEqual(1, PurgeSeq),
{ok, Db3} = couch_db:reopen(Db2),
{ok, PIdsRevs} = couch_db:fold_purged_docs(Db3, 0, fun fold_fun/2, [], []),
?assertEqual(0, couch_db_engine:get(Db3, doc_count)),
?assertEqual(0, couch_db_engine:get(Db3, del_doc_count)),
?assertEqual(2, couch_db_engine:get(Db3, update_seq)),
?assertEqual(1, couch_db_engine:get(Db3, purge_seq)),
?assertEqual([{DocId, [Rev]}], PIdsRevs)
end).
add_delete_purge(DbName) ->
?_test(
begin
{ok, Db0} = couch_db:open_int(DbName, []),
DocId = <<"foo">>,
{ok, Rev} = save_doc(Db0, {[{<<"_id">>, DocId}, {<<"vsn">>, 1}]}),
couch_db:ensure_full_commit(Db0),
{ok, Db1} = couch_db:reopen(Db0),
{ok, Rev2} = save_doc(Db1, {[{<<"_id">>, DocId}, {<<"vsn">>, 2},
{<<"_rev">>, couch_doc:rev_to_str(Rev)}, {<<"_deleted">>, true}]}),
couch_db:ensure_full_commit(Db1),
{ok, Db2} = couch_db:reopen(Db1),
{ok, PIdsRevs1} = couch_db:fold_purged_docs(Db2, 0, fun fold_fun/2, [], []),
?assertEqual(0, couch_db_engine:get(Db2, doc_count)),
?assertEqual(1, couch_db_engine:get(Db2, del_doc_count)),
?assertEqual(2, couch_db_engine:get(Db2, update_seq)),
?assertEqual(0, couch_db_engine:get(Db2, purge_seq)),
?assertEqual([], PIdsRevs1),
UUID = couch_uuids:new(),
{ok, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db2,
[{UUID, DocId, [Rev2]}]),
?assertEqual([Rev2], PRevs),
?assertEqual(1, PurgeSeq),
{ok, Db3} = couch_db:reopen(Db2),
{ok, PIdsRevs2} = couch_db:fold_purged_docs(Db3, 0, fun fold_fun/2, [], []),
?assertEqual(0, couch_db_engine:get(Db3, doc_count)),
?assertEqual(0, couch_db_engine:get(Db3, del_doc_count)),
?assertEqual(3, couch_db_engine:get(Db3, update_seq)),
?assertEqual(1, couch_db_engine:get(Db3, purge_seq)),
?assertEqual([{DocId, [Rev2]}], PIdsRevs2)
end).
add_two_purge_one(DbName) ->
?_test(
begin
{ok, Db} = couch_db:open_int(DbName, []),
{ok, Rev} = save_doc(Db, {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]}),
{ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"bar">>}]}),
couch_db:ensure_full_commit(Db),
{ok, Db2} = couch_db:reopen(Db),
?assertEqual(2, couch_db_engine:get(Db2, doc_count)),
?assertEqual(0, couch_db_engine:get(Db2, del_doc_count)),
?assertEqual(2, couch_db_engine:get(Db2, update_seq)),
?assertEqual(0, couch_db_engine:get(Db2, purge_seq)),
UUID = couch_uuids:new(),
{ok, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db2,
[{UUID, <<"foo">>, [Rev]}]),
?assertEqual([Rev], PRevs),
?assertEqual(1, PurgeSeq),
{ok, Db3} = couch_db:reopen(Db2),
{ok, PIdsRevs} = couch_db:fold_purged_docs(Db3, 0, fun fold_fun/2, [], []),
?assertEqual(1, couch_db_engine:get(Db3, doc_count)),
?assertEqual(0, couch_db_engine:get(Db3, del_doc_count)),
?assertEqual(3, couch_db_engine:get(Db3, update_seq)),
?assertEqual(1, couch_db_engine:get(Db3, purge_seq)),
?assertEqual([{<<"foo">>, [Rev]}], PIdsRevs)
end).
purge_id_not_exist(DbName) ->
?_test(
begin
{ok, Db} = couch_db:open_int(DbName, []),
UUID = couch_uuids:new(),
{ok, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db,
[{UUID, <<"foo">>, [{0, <<0>>}]}]),
?assertEqual([], PRevs),
?assertEqual(0, PurgeSeq),
{ok, Db2} = couch_db:reopen(Db),
{ok, PIdsRevs} = couch_db:fold_purged_docs(Db2, 0, fun fold_fun/2, [], []),
?assertEqual(0, couch_db_engine:get(Db2, doc_count)),
?assertEqual(0, couch_db_engine:get(Db2, del_doc_count)),
?assertEqual(0, couch_db_engine:get(Db2, update_seq)),
?assertEqual(0, couch_db_engine:get(Db2, purge_seq)),
?assertEqual([], PIdsRevs)
end).
purge_non_leaf_rev(DbName) ->
?_test(
begin
{ok, Db} = couch_db:open_int(DbName, []),
{ok, Rev} = save_doc(Db, {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]}),
couch_db:ensure_full_commit(Db),
{ok, Db2} = couch_db:reopen(Db),
{ok, _Rev2} = save_doc(Db2, {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 2},
{<<"_rev">>, couch_doc:rev_to_str(Rev)}]}),
couch_db:ensure_full_commit(Db2),
{ok, Db3} = couch_db:reopen(Db2),
UUID = couch_uuids:new(),
{ok, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db3,
[{UUID, <<"foo">>, [Rev]}]),
?assertEqual([], PRevs),
?assertEqual(0, PurgeSeq),
{ok, Db4} = couch_db:reopen(Db3),
{ok, PIdsRevs} = couch_db:fold_purged_docs(Db4, 0, fun fold_fun/2, [], []),
?assertEqual(1, couch_db_engine:get(Db4, doc_count)),
?assertEqual(2, couch_db_engine:get(Db4, update_seq)),
?assertEqual(0, couch_db_engine:get(Db4, purge_seq)),
?assertEqual([], PIdsRevs)
end).
purge_conflicts(DbName) ->
?_test(
begin
{ok, Db} = couch_db:open_int(DbName, []),
{ok, Rev} = save_doc(Db, {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, <<"v1.1">>}]}),
couch_db:ensure_full_commit(Db),
{ok, Db2} = couch_db:reopen(Db),
% create a conflict
DocConflict = #doc{
id = <<"foo">>,
revs = {1, [couch_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, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db3,
[{UUID, <<"foo">>, [Rev]}]),
?assertEqual([Rev], PRevs),
?assertEqual(1, PurgeSeq),
{ok, Db4} = couch_db:reopen(Db3),
{ok, PIdsRevs} = couch_db:fold_purged_docs(Db4, 0, fun fold_fun/2, [], []),
% still has one doc
?assertEqual(1, couch_db_engine:get(Db4, doc_count)),
?assertEqual(0, couch_db_engine:get(Db4, del_doc_count)),
?assertEqual(3, couch_db_engine:get(Db4, update_seq)),
?assertEqual(1, couch_db_engine:get(Db4, purge_seq)),
?assertEqual([{<<"foo">>, [Rev]}], PIdsRevs)
end).
purge_deep_tree(DbName) ->
?_test(
begin
NRevs = 300,
{ok, Db0} = couch_db:open_int(DbName, []),
{ok, InitRev} = save_doc(Db0, {[{<<"_id">>, <<"bar">>}, {<<"vsn">>, 0}]}),
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, {PurgeSeq, [{ok, PRevs}]}} = couch_db:purge_docs(Db1,
[{UUID, <<"bar">>, [LastRev]}]),
?assertEqual([LastRev], PRevs),
?assertEqual(1, PurgeSeq),
{ok, Db2} = couch_db:reopen(Db1),
% no docs left
?assertEqual(0, couch_db_engine:get(Db2, doc_count)),
?assertEqual(0, couch_db_engine:get(Db2, del_doc_count)),
?assertEqual(1, couch_db_engine:get(Db2, purge_seq)),
?assertEqual(NRevs + 1 , couch_db_engine:get(Db2, update_seq))
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(SourceDb3, doc_count)),
?assertEqual(1, couch_db_engine:get(SourceDb3, purge_seq)),
?assertEqual(1, couch_db_engine:get(TargetDb2, doc_count)),
?assertEqual(0, couch_db_engine:get(TargetDb2, purge_seq)),
% 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(SourceDb4, doc_count)),
?assertEqual(1, couch_db_engine:get(SourceDb4, purge_seq)),
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) ->
[{Id, Revs} | Acc].