Permalink
Browse files

Real fix for 'resurrection' of deleted doc

A PUT, DELETE, PUT sequence should result in a rev with generation 3
that's a child of the tombstone revision.
This fixes #207 and is the real fix for #205.
  • Loading branch information...
1 parent e8aa48b commit 5e05a8a650670c1ba926ceb0d183b34d4c41b8da @snej snej committed Jan 2, 2013
Showing with 33 additions and 29 deletions.
  1. +3 −0 Source/TDInternal.h
  2. +12 −29 Source/TD_Database+Insertion.m
  3. +10 −0 Source/TD_Database.m
  4. +8 −0 Source/TD_Database_Tests.m
View
3 Source/TDInternal.h
@@ -24,6 +24,9 @@
@property (readonly) TDBlobStore* attachmentStore;
- (BOOL) openFMDB;
- (SInt64) getDocNumericID: (NSString*)docID;
+- (SequenceNumber) getSequenceOfDocument: (SInt64)docNumericID
+ revision: (NSString*)revID
+ onlyCurrent: (BOOL)onlyCurrent;
- (TD_RevisionList*) getAllRevisionsOfDocumentID: (NSString*)docID
numericID: (SInt64)docNumericID
onlyCurrent: (BOOL)onlyCurrent;
View
41 Source/TD_Database+Insertion.m
@@ -293,7 +293,6 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
SInt64 docNumericID = docID ? [self getDocNumericID: docID] : 0;
BOOL oldWinnerWasDeletion = NO;
NSString* oldWinningRevID = nil;
- BOOL makeOldWinnerNonCurrent = NO;
if (docNumericID > 0) {
// Look up which rev is the winner, before this insertion
//OPT: This rev ID could be cached in the 'docs' row
@@ -308,10 +307,8 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
*outStatus = kTDStatusNotFound;
return nil;
}
- NSString* sql = $sprintf(@"SELECT sequence FROM revs "
- "WHERE doc_id=? AND revid=? %@ LIMIT 1",
- (allowConflict ? @"" : @"AND current=1"));
- parentSequence = [_fmdb longLongForQuery: sql, @(docNumericID), prevRevID];
+ parentSequence = [self getSequenceOfDocument: docNumericID revision: prevRevID
+ onlyCurrent: !allowConflict];
if (parentSequence == 0) {
// Not found: kTDStatusNotFound or a kTDStatusConflict, depending on whether there is any current revision
if (!allowConflict && [self existsDocumentWithID: docID revisionID: nil])
@@ -324,7 +321,7 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
if (_validations.count > 0) {
// Fetch the previous revision and validate the new one against it:
TD_Revision* prevRev = [[TD_Revision alloc] initWithDocID: docID revID: prevRevID
- deleted: NO];
+ deleted: NO];
status = [self validateRevision: rev previousRevision: prevRev];
if (TDStatusIsError(status)) {
*outStatus = status;
@@ -335,7 +332,7 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
} else {
// Inserting first revision.
if (deleted && docID) {
- // Didn't specify a revision to delete: kTDStatusNotFound or a kTDStatusConflict, depending
+ // Didn't specify a revision to delete: NotFound or a Conflict, depending
*outStatus = [self existsDocumentWithID: docID revisionID: nil] ? kTDStatusConflict : kTDStatusNotFound;
return nil;
}
@@ -357,7 +354,10 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
} else {
// Doc ID exists; check whether current winning revision is deleted:
if (oldWinnerWasDeletion) {
- makeOldWinnerNonCurrent = YES;
+ prevRevID = oldWinningRevID;
+ parentSequence = [self getSequenceOfDocument: docNumericID
+ revision: prevRevID
+ onlyCurrent: NO];
} else if (oldWinningRevID) {
// The current winning revision is not deleted, so this is a conflict
*outStatus = kTDStatusConflict;
@@ -420,25 +420,13 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
if (!sequence) {
// The insert failed. If it was due to a constraint violation, that means a revision
// already exists with identical contents and the same parent rev. We can ignore this
- // insert call, then, _unless_ it has the effect of un-deleting the document. [#205]
+ // insert call, then.
if (_fmdb.lastErrorCode != SQLITE_CONSTRAINT)
return nil;
LogTo(TD_Database, @"Duplicate rev insertion: %@ / %@", docID, newRevID);
- if (!oldWinnerWasDeletion || deleted) {
- // no-op
- *outStatus = kTDStatusOK;
- rev.body = nil;
- return rev;
- }
- sequence = [_fmdb longLongForQuery: @"SELECT sequence FROM revs "
- "WHERE doc_id=? and revid=?",
- @(docNumericID), newRevID];
- if (sequence <= 0)
- return nil;
- rev.sequence = sequence;
- // Make the old revision current again:
- if (![_fmdb executeUpdate: @"UPDATE revs SET current=1 WHERE sequence=?", @(sequence)])
- return nil;
+ *outStatus = kTDStatusOK;
+ rev.body = nil;
+ return rev;
}
// Make replaced rev non-current:
@@ -447,11 +435,6 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
@(parentSequence)])
return nil;
}
- if (makeOldWinnerNonCurrent) {
- if (![_fmdb executeUpdate: @"UPDATE revs SET current=0 WHERE doc_id=? and revid=?",
- @(docNumericID), oldWinningRevID])
- return nil;
- }
// Store any attachments:
status = [self processAttachments: attachments
View
10 Source/TD_Database.m
@@ -596,6 +596,16 @@ - (SInt64) getDocNumericID: (NSString*)docID {
}
+- (SequenceNumber) getSequenceOfDocument: (SInt64)docNumericID
+ revision: (NSString*)revID
+ onlyCurrent: (BOOL)onlyCurrent
+{
+ NSString* sql = $sprintf(@"SELECT sequence FROM revs WHERE doc_id=? AND revid=? %@ LIMIT 1",
+ (onlyCurrent ? @"AND current=1" : @""));
+ return [_fmdb longLongForQuery: sql, @(docNumericID), revID];
+}
+
+
#pragma mark - HISTORY:
View
8 Source/TD_Database_Tests.m
@@ -209,6 +209,14 @@
{@"_rev", rev2.revID},
{@"_deleted", $true},
{@"property", @"newvalue"}));
+ readRev = [db getDocumentWithID: rev2.docID revisionID: nil];
+ CAssertNil(readRev);
+
+ // Make sure it's possible to create the doc from scratch again:
+ TD_Revision* rev3 = putDoc(db, $dict({@"_id", rev1.docID}, {@"property", @"newvalue"}));
+ CAssert([rev3.revID hasPrefix: @"3-"]); // new rev is child of tombstone rev
+ readRev = [db getDocumentWithID: rev2.docID revisionID: nil];
+ CAssertEqual(readRev.revID, rev3.revID);
}

0 comments on commit 5e05a8a

Please sign in to comment.