Skip to content

Commit e9e5484

Browse files
committed
CBL-7841: Handle conflict when revision history appears broken
This is a fallback for cases where the general comparison algorithm yields a Conflict while applying a remote revision to the local document. The target scenario is a deleted document being resurrected in CBS -- when that happens, the resurrected document has no history prior to the deletion.
1 parent cabd447 commit e9e5484

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

LiteCore/Database/VectorDocument.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,58 @@ namespace litecore {
428428
} else {
429429
// Compare it with the current document revision:
430430
order = VersionVecWithLegacy::compare(newVers, curVers);
431+
432+
// CBL-8277: Handle the case where revision history may appear broken.
433+
//
434+
// Concrete scenario for a single document ID:
435+
// 1. Local DB deletes the doc.
436+
// 2. Local pushes the tombstone to a remote SGW.
437+
// 3. In the target CB database, a new doc is created with the same ID.
438+
// (In CBS, a deleted doc is effectively purged, so the ID is free
439+
// for reuse by an unrelated document.)
440+
// 4. Local DB performs a pull.
441+
//
442+
// At step 4 the BLIP message carries a document that starts at a new
443+
// version with no history. In general, this conflicts with whatever
444+
// revision Local currently holds, and it is indistinguishable from a
445+
// genuinely new document authored elsewhere (i.e. one that never went
446+
// through the sequence above). The following analysis shows how we can
447+
// recognize the sequence above and treat `newVers` as a newer revision
448+
// rather than a conflict.
449+
//
450+
// Proposition:
451+
// For a given document at a given remote, the set of authors in the
452+
// Version Vector may only grow.
453+
//
454+
// Applying the proposition here:
455+
// - `remote` : the remote index that `newVers` came from.
456+
// - `rev` : the latest revision we know we have shared with remote
457+
// `remote`.
458+
// - `newVers` : the revision arriving from `remote` via BLIP.
459+
//
460+
// Because the remote has already seen `rev`, its Version Vector must
461+
// contain `rev`'s author. Our general VV comparison relies on this. If
462+
// that author is now missing from `newVers`, it must have been dropped.
463+
// From this we can draw the following conservative conclusion:
464+
//
465+
// `newVers` is "newer" than any version that shares an author with
466+
// `rev` at the same or older age (lower or equal logical clock).
467+
//
468+
// In general, we further posit that is_newwer_or_same is transitive.
469+
// From the fact that `rev` has been seen by the remote,
470+
// `newVers` is_newer_or_same `rev`
471+
// Therefore, we can deduce that `newVers` is_newer_or_same `curVers`
472+
// if `rev` is_newer_same `curVers`. Since `same` can be determined by the
473+
// general comparison, we are only concerned here with is_newer.
474+
475+
if ( order == kConflicting && remote != RemoteID::Local ) {
476+
if ( optional<Revision> rev = _doc.loadRemoteRevision(remote); rev ) {
477+
VersionVecWithLegacy landmark{rev->revID};
478+
versionOrder orderWithLandmark = VersionVecWithLegacy::compare(landmark, curVers);
479+
if ( orderWithLandmark == kSame || orderWithLandmark == kNewer ) order = kNewer;
480+
}
481+
}
482+
431483
logPutExisting(curVers, newVers, order, remote);
432484

433485
// Check for no-op or conflict:

0 commit comments

Comments
 (0)