From 2f908cd15bfd5d78a2b42712e668c52405b67d16 Mon Sep 17 00:00:00 2001 From: "Damien F. Katz" Date: Mon, 23 Nov 2009 20:41:06 +0000 Subject: Fix and tests for COUCHDB-292 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@883494 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/recreate_doc.js | 51 ++++++++++++++++++++++++++++++++--- src/couchdb/couch_db.erl | 5 +++- src/couchdb/couch_db_updater.erl | 13 +++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/share/www/script/test/recreate_doc.js b/share/www/script/test/recreate_doc.js index 3786aba2..a6a64ac0 100644 --- a/share/www/script/test/recreate_doc.js +++ b/share/www/script/test/recreate_doc.js @@ -18,12 +18,12 @@ couchTests.recreate_doc = function(debug) { // First create a new document with the ID "foo", and delete it again var doc = {_id: "foo", a: "bar", b: 42}; - T(db.save(doc).ok); + var result = db.save(doc); + T(result.ok); + var firstRev = result.rev; T(db.deleteDoc(doc).ok); // Now create a new document with the same ID, save it, and then modify it - // This should work fine, but currently results in a conflict error, at - // least "sometimes" for (var i = 0; i < 10; i++) { doc = {_id: "foo"}; T(db.save(doc).ok); @@ -32,4 +32,49 @@ couchTests.recreate_doc = function(debug) { T(db.save(doc).ok); T(db.deleteDoc(doc).rev != undefined); } + + try { + // COUCHDB-292 now attempt to save the document with a prev that's since + // been deleted and this should generate a conflict exception + db.save({_id:"foo", _rev:firstRev, bar:1}); + T("no save conflict 1" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + var binAttDoc = { + _id: "foo", + _rev:firstRev, + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + } + try { + // same as before, but with binary + db.save(binAttDoc); + T("no save conflict 2" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + + try { + // random non-existant prev rev + db.save({_id:"foo", _rev:"1-asfafasdf", bar:1}); + T("no save conflict 3" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + try { + // random non-existant prev rev with bin + binAttDoc._rev = "1-aasasfasdf"; + db.save(binAttDoc); + T("no save conflict 4" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } }; diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index a729f215..f0827334 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -331,6 +331,8 @@ prep_and_validate_update(Db, #doc{id=Id,revs={RevStart, Revs}}=Doc, {validate_doc_update(Db, Doc, LoadDiskDoc), Doc} end; error when AllowConflict -> + couch_doc:merge_stubs(Doc, #doc{}), % will generate error if + % there are stubs {validate_doc_update(Db, Doc, fun() -> nil end), Doc}; error -> {conflict, Doc} @@ -396,7 +398,8 @@ prep_and_validate_updates(Db, [DocBucket|RestBuckets], end end, {[], AccErrors}, DocBucket), - prep_and_validate_updates(Db, RestBuckets, RestLookups, AllowConflict, [PreppedBucket | AccPrepped], AccErrors3). + prep_and_validate_updates(Db, RestBuckets, RestLookups, AllowConflict, + [PreppedBucket | AccPrepped], AccErrors3). update_docs(#db{update_pid=UpdatePid}=Db, Docs, Options) -> diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl index d9951cf6..94414f2e 100644 --- a/src/couchdb/couch_db_updater.erl +++ b/src/couchdb/couch_db_updater.erl @@ -465,6 +465,19 @@ merge_rev_trees(MergeConflicts, [NewDocs|RestDocsList], {_NewTree, conflicts} when (not OldDeleted) -> send_result(Client, Id, {Pos-1,PrevRevs}, conflict), AccTree; + {NewTree, conflicts} when PrevRevs /= [] -> + % Check to be sure if prev revision was specified, it's + % a leaf node in the tree + Leafs = couch_key_tree:get_all_leafs(AccTree), + IsPrevLeaf = lists:any(fun({_, {LeafPos, [LeafRevId|_]}}) -> + {LeafPos, LeafRevId} == {Pos-1, hd(PrevRevs)} + end, Leafs), + if IsPrevLeaf -> + NewTree; + true -> + send_result(Client, Id, {Pos-1,PrevRevs}, conflict), + AccTree + end; {NewTree, no_conflicts} when AccTree == NewTree -> % the tree didn't change at all % meaning we are saving a rev that's already -- cgit v1.2.3