public static ChangeVectorEntry[] MergeVectors(ChangeVectorEntry[] vectorA, ChangeVectorEntry[] vectorB) { var merged = new ChangeVectorEntry[Math.Max(vectorA.Length, vectorB.Length)]; var inx = 0; foreach (var entryA in vectorA) { var etagA = entryA.Etag; ChangeVectorEntry first = new ChangeVectorEntry(); foreach (var e in vectorB) { if (e.DbId == entryA.DbId) { first = e; break; } } var etagB = first.Etag; merged[inx++] = new ChangeVectorEntry { DbId = entryA.DbId, Etag = Math.Max(etagA, etagB) }; } return(merged); }
public static ChangeVectorEntry[] ReadChangeVectorFrom(Tree tree) { var changeVector = new ChangeVectorEntry[tree.State.NumberOfEntries]; using (var iter = tree.Iterate(false)) { if (iter.Seek(Slices.BeforeAllKeys) == false) { return(changeVector); } var buffer = new byte[sizeof(Guid)]; int index = 0; do { var read = iter.CurrentKey.CreateReader().Read(buffer, 0, sizeof(Guid)); if (read != sizeof(Guid)) { throw new InvalidDataException($"Expected guid, but got {read} bytes back for change vector"); } changeVector[index].DbId = new Guid(buffer); changeVector[index].Etag = iter.CreateReaderForCurrent().ReadBigEndianInt64(); index++; } while (iter.MoveNext()); } return(changeVector); }
public async Task PUT_of_conflicted_document_with_outdated_etag_throws_concurrency_exception() { using (var storeA = GetDocumentStore()) using (var storeB = GetDocumentStore()) { using (var session = storeA.OpenSession()) { session.Store(new User { Name = "John Doe" }, "users/1"); session.SaveChanges(); } using (var session = storeB.OpenSession()) { session.Store(new User { Name = "Jane Doe" }, "users/1"); session.SaveChanges(); } await SetupReplicationAsync(storeA, storeB); await SetupReplicationAsync(storeB, storeA); WaitUntilHasConflict(storeA, "users/1"); long maxConflictEtag; using (var session = storeA.OpenSession()) { var ex = Assert.Throws <DocumentConflictException>(() => session.Load <User>("users/1")); maxConflictEtag = ex.LargestEtag; } //should throw concurrency exception because we use lower etag then max etag of existing conflicts using (var session = storeA.OpenSession()) { var db = GetDocumentDatabaseInstanceFor(storeA).Result; var cv = new ChangeVectorEntry[1]; cv[0] = new ChangeVectorEntry { DbId = db.DbBase64Id, Etag = maxConflictEtag - 1 }; session.Store(new User { Name = "James Doe" }, cv.SerializeVector(), "users/1"); Assert.Throws <ConcurrencyException>(() => session.SaveChanges()); } //now this should _not_ throw, since we do not specify expected conflict etag, so... using (var session = storeA.OpenSession()) { session.Store(new User { Name = "James Doe" }, "users/1"); session.SaveChanges(); } } }
public static ChangeVectorEntry[] GetChangeVectorEntriesFromTableValueReader(TableValueReader tvr, int index) { int size; var pChangeVector = (ChangeVectorEntry *)tvr.Read(index, out size); var changeVector = new ChangeVectorEntry[size / sizeof(ChangeVectorEntry)]; for (int i = 0; i < changeVector.Length; i++) { changeVector[i] = pChangeVector[i]; } return(changeVector); }
private ChangeVectorEntry[] GetMergedConflictChangeVectorsAndDeleteConflicts(Transaction tx, TransactionOperationContext context, Slice name, long newEtag, ChangeVectorEntry[] existing = null) { var conflictChangeVectors = DeleteConflictsFor(tx, name); //no conflicts, no need to merge if (conflictChangeVectors.Count == 0) { if (existing != null) { return(ReplicationUtils.UpdateChangeVectorWithNewEtag(_environment.DbId, newEtag, existing)); } return(new[] { new ChangeVectorEntry { Etag = newEtag, DbId = _environment.DbId } }); } // need to merge the conflict change vectors var maxEtags = new Dictionary <Guid, long> { [_environment.DbId] = newEtag }; foreach (var conflictChangeVector in conflictChangeVectors) { foreach (var entry in conflictChangeVector) { long etag; if (maxEtags.TryGetValue(entry.DbId, out etag) == false || etag < entry.Etag) { maxEtags[entry.DbId] = entry.Etag; } } } var changeVector = new ChangeVectorEntry[maxEtags.Count]; var index = 0; foreach (var maxEtag in maxEtags) { changeVector[index].DbId = maxEtag.Key; changeVector[index].Etag = maxEtag.Value; index++; } return(changeVector); }
public async Task DELETE_of_conflicted_document_with_outdated_etag_throws_concurrency_exception() { using (var storeA = GetDocumentStore(options: new Options { ModifyDatabaseRecord = record => { record.ConflictSolverConfig = new ConflictSolver { ResolveToLatest = false, ResolveByCollection = new Dictionary <string, ScriptResolver>() }; } })) using (var storeB = GetDocumentStore(options: new Options { ModifyDatabaseRecord = record => { record.ConflictSolverConfig = new ConflictSolver { ResolveToLatest = false, ResolveByCollection = new Dictionary <string, ScriptResolver>() }; } })) { using (var session = storeA.OpenSession()) { session.Store(new User { Name = "John Doe" }, "users/1"); session.SaveChanges(); } using (var session = storeB.OpenSession()) { session.Store(new User { Name = "Jane Doe" }, "users/1"); session.SaveChanges(); } await SetupReplicationAsync(storeA, storeB); await SetupReplicationAsync(storeB, storeA); WaitUntilHasConflict(storeA, "users/1"); long maxConflictEtag; using (var session = storeA.OpenSession()) { var ex = Assert.Throws <DocumentConflictException>(() => session.Load <User>("users/1")); maxConflictEtag = ex.LargestEtag; } //should throw concurrency exception because we use lower etag then max etag of existing conflicts using (var session = storeA.OpenSession()) { var db = Databases.GetDocumentDatabaseInstanceFor(storeA).Result; var cv = new ChangeVectorEntry[1]; cv[0] = new ChangeVectorEntry { DbId = db.DbBase64Id, Etag = maxConflictEtag - 1 }; session.Delete("users/1", cv.SerializeVector()); Assert.Throws <ConcurrencyException>(() => session.SaveChanges()); } //now this should _not_ throw, since we do not specify expected conflict etag, so... using (var session = storeA.OpenSession()) { session.Delete("users/1"); session.SaveChanges(); } } }