You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by fd...@apache.org on 2012/01/07 14:13:19 UTC
[2/3] git commit: Fix document deletion followed by creation
Fix document deletion followed by creation
If the updater collects 2 updates for the same document
where the first deletes it and the second creates it, it
would send a conflict error to the client of the create
request. This wouldn't happen if the 2 requests were
collected in 2 different batches by the updater.
Closes COUCHDB-188
Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/6d1d23b7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/6d1d23b7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/6d1d23b7
Branch: refs/heads/master
Commit: 6d1d23b7d0eba4de3c4097907adc37b09191196e
Parents: b64643a
Author: Filipe David Borba Manana <fd...@apache.org>
Authored: Fri Jan 6 12:22:29 2012 +0000
Committer: Filipe David Borba Manana <fd...@apache.org>
Committed: Sat Jan 7 12:46:04 2012 +0000
----------------------------------------------------------------------
src/couchdb/couch_db_updater.erl | 22 +++++-----
test/etap/074-doc-update-conflicts.t | 62 ++++++++++++++++++++++++++++-
2 files changed, 72 insertions(+), 12 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6d1d23b7/src/couchdb/couch_db_updater.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl
index e57f05b..0b890fe 100644
--- a/src/couchdb/couch_db_updater.erl
+++ b/src/couchdb/couch_db_updater.erl
@@ -576,16 +576,16 @@ merge_rev_trees(_Limit, _Merge, [], [], AccNewInfos, AccRemoveSeqs, AccSeq) ->
{ok, lists:reverse(AccNewInfos), AccRemoveSeqs, AccSeq};
merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
[OldDocInfo|RestOldInfo], AccNewInfos, AccRemoveSeqs, AccSeq) ->
- #full_doc_info{id=Id,rev_tree=OldTree,deleted=OldDeleted,update_seq=OldSeq}
+ #full_doc_info{id=Id,rev_tree=OldTree,deleted=OldDeleted0,update_seq=OldSeq}
= OldDocInfo,
- NewRevTree = lists:foldl(
- fun({Client, {#doc{revs={Pos,[_Rev|PrevRevs]}}=NewDoc, Ref}}, AccTree) ->
+ {NewRevTree, _} = lists:foldl(
+ fun({Client, {#doc{revs={Pos,[_Rev|PrevRevs]}}=NewDoc, Ref}}, {AccTree, OldDeleted}) ->
if not MergeConflicts ->
case couch_key_tree:merge(AccTree, couch_doc:to_path(NewDoc),
Limit) of
{_NewTree, conflicts} when (not OldDeleted) ->
send_result(Client, Ref, conflict),
- AccTree;
+ {AccTree, OldDeleted};
{NewTree, conflicts} when PrevRevs /= [] ->
% Check to be sure if prev revision was specified, it's
% a leaf node in the tree
@@ -594,10 +594,10 @@ merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
{LeafPos, LeafRevId} == {Pos-1, hd(PrevRevs)}
end, Leafs),
if IsPrevLeaf ->
- NewTree;
+ {NewTree, OldDeleted};
true ->
send_result(Client, Ref, conflict),
- AccTree
+ {NewTree, OldDeleted}
end;
{NewTree, no_conflicts} when AccTree == NewTree ->
% the tree didn't change at all
@@ -616,21 +616,21 @@ merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
couch_doc:to_path(NewDoc2), Limit),
% we changed the rev id, this tells the caller we did
send_result(Client, Ref, {ok, {OldPos + 1, NewRevId}}),
- NewTree2;
+ {NewTree2, OldDeleted};
true ->
send_result(Client, Ref, conflict),
- AccTree
+ {NewTree, OldDeleted}
end;
{NewTree, _} ->
- NewTree
+ {NewTree, NewDoc#doc.deleted}
end;
true ->
{NewTree, _} = couch_key_tree:merge(AccTree,
couch_doc:to_path(NewDoc), Limit),
- NewTree
+ {NewTree, OldDeleted}
end
end,
- OldTree, NewDocs),
+ {OldTree, OldDeleted0}, NewDocs),
if NewRevTree == OldTree ->
% nothing changed
merge_rev_trees(Limit, MergeConflicts, RestDocsList, RestOldInfo,
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6d1d23b7/test/etap/074-doc-update-conflicts.t
----------------------------------------------------------------------
diff --git a/test/etap/074-doc-update-conflicts.t b/test/etap/074-doc-update-conflicts.t
index 7c2f80d..09d0633 100755
--- a/test/etap/074-doc-update-conflicts.t
+++ b/test/etap/074-doc-update-conflicts.t
@@ -26,7 +26,7 @@ test_db_name() -> <<"couch_test_update_conflicts">>.
main(_) ->
test_util:init_code_path(),
- etap:plan(25),
+ etap:plan(35),
case (catch test()) of
ok ->
etap:end_tests();
@@ -45,6 +45,8 @@ test() ->
fun(NumClients) -> test_concurrent_doc_update(NumClients) end,
[100, 500, 1000, 2000, 5000]),
+ test_bulk_delete_create(),
+
couch_server_sup:stop(),
ok.
@@ -132,6 +134,64 @@ test_concurrent_doc_update(NumClients) ->
delete_db(Db3).
+% COUCHDB-188
+test_bulk_delete_create() ->
+ {ok, Db} = create_db(test_db_name()),
+ Doc = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"foobar">>},
+ {<<"value">>, 0}
+ ]}),
+ {ok, Rev} = couch_db:update_doc(Db, Doc, []),
+
+ DeletedDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"foobar">>},
+ {<<"_rev">>, couch_doc:rev_to_str(Rev)},
+ {<<"_deleted">>, true}
+ ]}),
+ NewDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"foobar">>},
+ {<<"value">>, 666}
+ ]}),
+
+ {ok, Results} = couch_db:update_docs(Db, [DeletedDoc, NewDoc], []),
+ ok = couch_db:close(Db),
+
+ etap:is(length([ok || {ok, _} <- Results]), 2,
+ "Deleted and non-deleted versions got an ok reply"),
+
+ [{ok, Rev1}, {ok, Rev2}] = Results,
+ {ok, Db2} = couch_db:open_int(test_db_name(), []),
+
+ {ok, [{ok, Doc1}]} = couch_db:open_doc_revs(
+ Db2, <<"foobar">>, [Rev1], [conflicts, deleted_conflicts]),
+ {ok, [{ok, Doc2}]} = couch_db:open_doc_revs(
+ Db2, <<"foobar">>, [Rev2], [conflicts, deleted_conflicts]),
+ ok = couch_db:close(Db2),
+
+ {Doc1Props} = couch_doc:to_json_obj(Doc1, []),
+ {Doc2Props} = couch_doc:to_json_obj(Doc2, []),
+
+ etap:is(couch_util:get_value(<<"_deleted">>, Doc1Props), true,
+ "Document was deleted"),
+ etap:is(couch_util:get_value(<<"_deleted">>, Doc2Props), undefined,
+ "New document not flagged as deleted"),
+ etap:is(couch_util:get_value(<<"value">>, Doc2Props), 666,
+ "New leaf revision has the right value"),
+ etap:is(couch_util:get_value(<<"_conflicts">>, Doc1Props), undefined,
+ "Deleted document has no conflicts"),
+ etap:is(couch_util:get_value(<<"_deleted_conflicts">>, Doc1Props), undefined,
+ "Deleted document has no deleted conflicts"),
+ etap:is(couch_util:get_value(<<"_conflicts">>, Doc2Props), undefined,
+ "New leaf revision doesn't have conflicts"),
+ etap:is(couch_util:get_value(<<"_deleted_conflicts">>, Doc2Props), undefined,
+ "New leaf revision doesn't have deleted conflicts"),
+
+ etap:is(element(1, Rev1), 2, "Deleted revision has position 2"),
+ etap:is(element(1, Rev2), 1, "New leaf revision has position 1"),
+
+ delete_db(Db2).
+
+
spawn_client(Doc) ->
spawn(fun() ->
{ok, Db} = couch_db:open_int(test_db_name(), []),