summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien F. Katz <damien@apache.org>2009-11-23 20:41:06 +0000
committerDamien F. Katz <damien@apache.org>2009-11-23 20:41:06 +0000
commit2f908cd15bfd5d78a2b42712e668c52405b67d16 (patch)
treeaf3328bef809e7a06d840a29fc345abb0f61c6b7
parent3a4fad07cb5097598eefd0cd7f588c5881a61c8f (diff)
Fix and tests for COUCHDB-292
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@883494 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--share/www/script/test/recreate_doc.js51
-rw-r--r--src/couchdb/couch_db.erl5
-rw-r--r--src/couchdb/couch_db_updater.erl13
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