Permalink
Browse files

Merge branch 'master' into stable

  • Loading branch information...
2 parents 8d6942e + 5e05a8a commit e852e4a111777542f45785aa89e1c069133b1945 @snej snej committed Jan 2, 2013
Showing with 83 additions and 50 deletions.
  1. +3 −0 Source/TDInternal.h
  2. +8 −5 Source/TDRouter+Handlers.m
  3. +1 −0 Source/TDStatus.m
  4. +34 −41 Source/TD_Database+Insertion.m
  5. +10 −0 Source/TD_Database.m
  6. +26 −3 Source/TD_Database_Tests.m
  7. +1 −1 vendor/fmdb
View
@@ -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
@@ -295,14 +295,17 @@ - (TDStatus) do_POST_bulk_docs: (TD_Database*)db {
Assert(rev.revID);
if (!noNewEdits)
result = $dict({@"id", rev.docID}, {@"rev", rev.revID}, {@"ok", $true});
+ } else if (status >= 500) {
+ return status; // abort the whole thing if something goes badly wrong
} else if (allOrNothing) {
return status; // all_or_nothing backs out if there's any error
- } else if (status == kTDStatusForbidden) {
- result = $dict({@"id", docID}, {@"error", @"validation failed"});
- } else if (status == kTDStatusConflict) {
- result = $dict({@"id", docID}, {@"error", @"conflict"});
} else {
- return status; // abort the whole thing if something goes badly wrong
+ NSString* error = nil;
+ if (status == kTDStatusForbidden)
+ error = @"validation failed";
+ else
+ TDStatusToHTTPStatus(status, &error);
+ result = $dict({@"id", docID}, {@"error", error});
}
if (result)
[results addObject: result];
View
@@ -27,6 +27,7 @@
static const struct StatusMapEntry kStatusMap[] = {
{kTDStatusNotFound, 404, "not_found"}, // for compatibility with CouchDB
+ {kTDStatusConflict, 409, "conflict"},
{kTDStatusDuplicate, 412, "Already exists"}, // really 'Precondition Failed'
{kTDStatusBadEncoding, 400, "Bad data encoding"},
@@ -288,19 +288,27 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
TD_Revision* winningRev = nil;
@try {
//// PART I: In which are performed lookups and validations prior to the insert...
-
+
+ // Get the doc's numeric ID (doc_id) and its current winning revision:
SInt64 docNumericID = docID ? [self getDocNumericID: docID] : 0;
+ BOOL oldWinnerWasDeletion = NO;
+ NSString* oldWinningRevID = nil;
+ if (docNumericID > 0) {
+ // Look up which rev is the winner, before this insertion
+ //OPT: This rev ID could be cached in the 'docs' row
+ oldWinningRevID = [self winningRevIDOfDocNumericID: docNumericID
+ isDeleted: &oldWinnerWasDeletion];
+ }
+
SequenceNumber parentSequence = 0;
if (prevRevID) {
// Replacing: make sure given prevRevID is current & find its sequence number:
if (docNumericID <= 0) {
*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])
@@ -313,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;
@@ -324,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;
}
@@ -345,25 +353,16 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
return nil;
} else {
// Doc ID exists; check whether current winning revision is deleted:
- r = [_fmdb executeQuery: @"SELECT sequence, deleted FROM revs "
- "WHERE doc_id=? and current=1 ORDER BY revid DESC LIMIT 1",
- @(docNumericID)];
- if (!r)
+ if (oldWinnerWasDeletion) {
+ 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;
return nil;
- if ([r next]) {
- if ([r boolForColumnIndex: 1]) {
- // Make the deleted revision no longer current:
- if (![_fmdb executeUpdate: @"UPDATE revs SET current=0 WHERE sequence=?",
- @([r longLongIntForColumnIndex: 0])])
- return nil;
- } else if (!allowConflict) {
- // The current winning revision is not deleted, so this is a conflict
- *outStatus = kTDStatusConflict;
- return nil;
- }
}
- [r close];
- r = nil;
}
} else {
// Inserting first revision, with no docID given (POST): generate a unique docID:
@@ -374,13 +373,7 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
}
}
- // Look up which rev is the winner, before this insertion
- //OPT: This rev ID could be cached in the 'docs' row
- BOOL oldWinnerWasDeletion;
- NSString* oldWinningRevID = [self winningRevIDOfDocNumericID: docNumericID
- isDeleted: &oldWinnerWasDeletion];
-
- //// PART II: In which insertion occurs...
+ //// PART II: In which we prepare for insertion...
// Get the attachments:
NSDictionary* attachments = [self attachmentsFromRevision: rev status: &status];
@@ -411,29 +404,29 @@ - (TD_Revision*) putRevision: (TD_Revision*)rev
Assert(docID);
rev = [rev copyWithDocID: docID revID: newRevID];
- // Now insert the rev itself:
-
// Don't store a SQL null in the 'json' column -- I reserve it to mean that the revision data
// is missing due to compaction or replication.
// Instead, store an empty zero-length blob.
if (json == nil)
json = [NSData data];
+ //// PART III: In which the actual insertion finally takes place:
+
SequenceNumber sequence = [self insertRevision: rev
docNumericID: docNumericID
parentSequence: parentSequence
current: YES
JSON: json];
if (!sequence) {
- // The insert failed. If it was due to a constraint violation, that means an identical
- // revision already exists; so just return it.
- if (_fmdb.lastErrorCode == SQLITE_CONSTRAINT) {
- *outStatus = kTDStatusOK;
- rev.body = nil;
- return rev;
- } else {
+ // 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.
+ if (_fmdb.lastErrorCode != SQLITE_CONSTRAINT)
return nil;
- }
+ LogTo(TD_Database, @"Duplicate rev insertion: %@ / %@", docID, newRevID);
+ *outStatus = kTDStatusOK;
+ rev.body = nil;
+ return rev;
}
// Make replaced rev non-current:
View
@@ -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
@@ -54,10 +54,12 @@
TD_Revision* rev = [[TD_Revision alloc] initWithProperties: props];
TDStatus status;
TD_Revision* result = [db putRevision: rev
- prevRevisionID: props[@"_rev"]
- allowConflict: NO
- status: &status];
+ prevRevisionID: props[@"_rev"]
+ allowConflict: NO
+ status: &status];
CAssert(status < 300);
+ CAssert(result.sequence > 0);
+ CAssert(result.revID != nil);
return result;
}
@@ -207,6 +209,27 @@
{@"_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);
+}
+
+
+TestCase(TD_Database_DeleteAndRecreate) {
+ // Test case for issue #205: Create a doc, delete it, create it again with the same content.
+ TD_Database* db = createDB();
+ TD_Revision* rev1 = putDoc(db, $dict({@"_id", @"dock"}, {@"property", @"value"}));
+ Log(@"Created: %@ -- %@", rev1, rev1.properties);
+ TD_Revision* rev2 = putDoc(db, $dict({@"_id", @"dock"}, {@"_rev", rev1.revID},
+ {@"_deleted", $true}));
+ Log(@"Deleted: %@ -- %@", rev2, rev2.properties);
+ TD_Revision* rev3 = putDoc(db, $dict({@"_id", @"dock"}, {@"property", @"value"}));
+ Log(@"Recreated: %@ -- %@", rev3, rev3.properties);
}
Submodule fmdb updated 1 files
+3 −0 src/FMDatabase.m

0 comments on commit e852e4a

Please sign in to comment.