public void TestResolveConflict() { var properties = new Dictionary <string, object>() { { "testName", "testCreateRevisions" }, { "tag", 1337 } }; // Create a conflict on purpose var doc = database.CreateDocument(); var newRev1 = doc.CreateRevision(); newRev1.SetUserProperties(properties); var rev1 = newRev1.Save(); var rev2a = CreateRevisionWithRandomProps(rev1, false); var rev2b = CreateRevisionWithRandomProps(rev1, true); SavedRevision winningRev = null; SavedRevision losingRev = null; if (doc.CurrentRevisionId.Equals(rev2a.Id)) { winningRev = rev2a; losingRev = rev2b; } else { winningRev = rev2b; losingRev = rev2a; } Assert.AreEqual(2, doc.ConflictingRevisions.Count()); Assert.AreEqual(2, doc.GetLeafRevisions(true).Count); // let's manually choose the losing rev as the winner. First, delete winner, which will // cause losing rev to be the current revision. var deleteRevision = winningRev.DeleteDocument(); Assert.AreEqual(1, doc.ConflictingRevisions.Count()); Assert.AreEqual(2, doc.GetLeafRevisions(true).Count); Assert.AreEqual(3, RevisionID.GetGeneration(deleteRevision.Id)); Assert.AreEqual(losingRev.Id, doc.CurrentRevisionId); // Finally create a new revision rev3 based on losing rev var rev3 = CreateRevisionWithRandomProps(losingRev, true); Assert.AreEqual(rev3.Id, doc.CurrentRevisionId); Assert.AreEqual(1, doc.ConflictingRevisions.Count()); Assert.AreEqual(2, doc.GetLeafRevisions(true).Count); }
/// <summary> /// Returns document by the specified docid from the specified db. Unless you request a /// specific revision, the latest revision of the document will always be returned. /// </summary> /// <returns>The response state for further HTTP processing</returns> /// <param name="context">The context of the Couchbase Lite HTTP request</param> /// <remarks> /// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid /// <remarks> public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context) { return(DatabaseMethods.PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); string docId = context.DocumentName; bool isLocalDoc = docId.StartsWith("_local"); DocumentContentOptions options = context.ContentOptions; string openRevsParam = context.GetQueryParam("open_revs"); bool mustSendJson = context.ExplicitlyAcceptsType("application/json"); if (openRevsParam == null || isLocalDoc) { //Regular GET: string revId = context.GetQueryParam("rev"); //often null RevisionInternal rev; bool includeAttachments = false, sendMultipart = false; if (isLocalDoc) { rev = db.Storage.GetLocalDocument(docId, revId); } else { includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments); if (includeAttachments) { sendMultipart = !mustSendJson; options &= ~DocumentContentOptions.IncludeAttachments; } Status status = new Status(); rev = db.GetDocument(docId, revId, true, status); if (rev != null) { rev = ApplyOptions(options, rev, context, db, status); } if (rev == null) { if (status.Code == StatusCode.Deleted) { response.StatusReason = "deleted"; } else { response.StatusReason = "missing"; } response.InternalStatus = status.Code; return response; } } if (rev == null) { response.InternalStatus = StatusCode.NotFound; return response; } if (context.CacheWithEtag(rev.RevID)) { response.InternalStatus = StatusCode.NotModified; return response; } if (!isLocalDoc && includeAttachments) { int minRevPos = 1; IList <string> attsSince = context.GetJsonQueryParam("atts_since").AsList <string>(); string ancestorId = db.Storage.FindCommonAncestor(rev, attsSince); if (ancestorId != null) { minRevPos = RevisionID.GetGeneration(ancestorId) + 1; } bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false); db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo); } if (sendMultipart) { response.MultipartWriter = MultipartWriterForRev(db, rev, "multipart/related"); } else { response.JsonBody = rev.GetBody(); } } else { // open_revs query: IList <IDictionary <string, object> > result; if (openRevsParam.Equals("all")) { // ?open_revs=all returns all current/leaf revisions: bool includeDeleted = context.GetQueryParam <bool>("include_deleted", bool.TryParse, false); RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true); result = new List <IDictionary <string, object> >(); foreach (var rev in allRevs) { if (!includeDeleted && rev.Deleted) { continue; } Status status = new Status(); var loadedRev = db.RevisionByLoadingBody(rev, status); if (loadedRev != null) { ApplyOptions(options, loadedRev, context, db, status); } if (loadedRev != null) { result.Add(new Dictionary <string, object> { { "ok", loadedRev.GetProperties() } }); } else if (status.Code <= StatusCode.InternalServerError) { result.Add(new Dictionary <string, object> { { "missing", rev.RevID } }); } else { response.InternalStatus = status.Code; return response; } } } else { // ?open_revs=[...] returns an array of specific revisions of the document: var openRevs = context.GetJsonQueryParam("open_revs").AsList <object>(); if (openRevs == null) { response.InternalStatus = StatusCode.BadParam; return response; } result = new List <IDictionary <string, object> >(); foreach (var revIDObj in openRevs) { var revID = revIDObj as string; if (revID == null) { response.InternalStatus = StatusCode.BadId; return response; } Status status = new Status(); var rev = db.GetDocument(docId, revID, true); if (rev != null) { rev = ApplyOptions(options, rev, context, db, status); } if (rev != null) { result.Add(new Dictionary <string, object> { { "ok", rev.GetProperties() } }); } else { result.Add(new Dictionary <string, object> { { "missing", revID } }); } } } if (mustSendJson) { response["Content-Type"] = "application/json"; response.JsonBody = new Body(result.Cast <object>().ToList()); } else { response.SetMultipartBody(result.Cast <object>().ToList(), "multipart/mixed"); } } return response; }).AsDefaultState()); }