Beispiel #1
0
        public string FindCommonAncestor(RevisionInternal rev, IEnumerable <string> revIds)
        {
            var generation = RevisionInternal.GenerationFromRevID(rev.GetRevId());
            var revIdArray = revIds == null ? null : revIds.ToList();

            if (generation <= 1 || revIdArray == null || revIdArray.Count == 0)
            {
                return(null);
            }

            revIdArray.Sort(RevisionInternal.CBLCompareRevIDs);
            var commonAncestor = default(string);

            WithC4Document(rev.GetDocId(), null, false, false, doc =>
            {
                foreach (var possibleRevId in revIds)
                {
                    if (RevisionInternal.GenerationFromRevID(possibleRevId) <= generation &&
                        Native.c4doc_selectRevision(doc, possibleRevId, false, null))
                    {
                        commonAncestor = possibleRevId;
                        return;
                    }
                }
            });

            return(commonAncestor);
        }
Beispiel #2
0
        public IEnumerable <string> GetPossibleAncestors(RevisionInternal rev, int limit, bool onlyAttachments)
        {
            var returnedCount = 0;
            var generation    = RevisionInternal.GenerationFromRevID(rev.GetRevId());
            var enumerator    = GetHistoryEnumerator(rev, generation);

            if (enumerator == null)
            {
                yield break;
            }

            foreach (var next in enumerator)
            {
                if (returnedCount >= limit)
                {
                    break;
                }

                var revId = next.CurrentRevID;
                if (RevisionInternal.GenerationFromRevID(revId) < generation &&
                    !next.SelectedRev.IsDeleted && next.HasRevisionBody &&
                    !(onlyAttachments && !next.SelectedRev.HasAttachments))
                {
                    returnedCount++;
                    yield return(revId);
                }
            }
        }
Beispiel #3
0
        public RevisionInternal PutLocalRevision(RevisionInternal revision, string prevRevId, bool obeyMVCC)
        {
            var docId = revision.GetDocId();

            if (!docId.StartsWith("_local/"))
            {
                throw new CouchbaseLiteException("Local revision IDs must start with _local/", StatusCode.BadId);
            }

            if (revision.IsDeleted())
            {
                DeleteLocalRevision(docId, prevRevId, obeyMVCC);
                return(revision);
            }

            var result = default(RevisionInternal);

            RunInTransaction(() =>
            {
                var json = Manager.GetObjectMapper().WriteValueAsString(revision.GetProperties(), true);
                WithC4Raw(docId, "_local", doc =>
                {
                    var generation = RevisionInternal.GenerationFromRevID(prevRevId);
                    if (obeyMVCC)
                    {
                        if (prevRevId != null)
                        {
                            if (prevRevId != (doc != null ? (string)doc->meta : null))
                            {
                                throw new CouchbaseLiteException(StatusCode.Conflict);
                            }

                            if (generation == 0)
                            {
                                throw new CouchbaseLiteException(StatusCode.BadId);
                            }
                        }
                        else if (doc != null)
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }

                    var newRevId = String.Format("{0}-local", ++generation);
                    ForestDBBridge.Check(err => Native.c4raw_put(Forest, "_local", docId, newRevId, json, err));
                    result = revision.CopyWithDocID(docId, newRevId);
                });

                return(true);
            });

            return(result);
        }
        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, RevisionInternal.GenerationFromRevID(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);
        }
Beispiel #5
0
        /// <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.GetLocalDocument(docId, revId);
                    }
                    else
                    {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if (includeAttachments)
                        {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }

                        Status status = new Status();
                        rev = db.GetDocumentWithIDAndRev(docId, revId, options, status);
                        if (rev != null)
                        {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if (rev == null)
                        {
                            if (status.GetCode() == StatusCode.Deleted)
                            {
                                response.StatusReason = "deleted";
                            }
                            else
                            {
                                response.StatusReason = "missing";
                            }

                            response.InternalStatus = status.GetCode();
                            return response;
                        }
                    }

                    if (rev == null)
                    {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    if (context.CacheWithEtag(rev.GetRevId()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if (!isLocalDoc && includeAttachments)
                    {
                        int minRevPos = 1;
                        IList <string> attsSince = context.GetJsonQueryParam("atts_since").AsList <string>();
                        string ancestorId = db.FindCommonAncestor(rev, attsSince);
                        if (ancestorId != null)
                        {
                            minRevPos = RevisionInternal.GenerationFromRevID(ancestorId) + 1;
                        }

                        Status status = new Status();
                        bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                        if (!db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo, status))
                        {
                            response.InternalStatus = status.GetCode();
                            return response;
                        }
                    }

                    if (sendMultipart)
                    {
                        response.MultipartWriter = db.MultipartWriterForRev(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.GetAllRevisionsOfDocumentID(docId, true);

                        result = new List <IDictionary <string, object> >();
                        foreach (var rev in allRevs)
                        {
                            if (!includeDeleted && rev.IsDeleted())
                            {
                                continue;
                            }

                            Status status = new Status();
                            RevisionInternal 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.GetCode() <= StatusCode.InternalServerError)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", rev.GetRevId() }
                                });
                            }
                            else
                            {
                                response.InternalStatus = status.GetCode();
                                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.GetDocumentWithIDAndRev(docId, revID, DocumentContentOptions.None, status);
                            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());
        }