Пример #1
0
        public void TestCBLCompareRevIDs()
        {
            // Single Digit
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("1-foo", "1-foo") == 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("2-bar", "1-foo") > 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("1-foo", "2-bar") < 0);

            // Multi-digit:
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("123-bar", "456-foo") < 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("456-foo", "123-bar") > 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("456-foo", "456-foo") == 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("456-foo", "456-foofoo") < 0);

            // Different numbers of digits:
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("89-foo", "123-bar") < 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("123-bar", "89-foo") > 0);

            // Edge cases:
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("123-", "89-") > 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("123-a", "123-a") == 0);

            // Invalid rev IDs:
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("-a", "-b") < 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("-", "-") == 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs(string.Empty, string.Empty) == 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs(string.Empty, "-b") < 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("bogus", "yo") < 0);
            Assert.IsTrue(RevisionID.CBLCollateRevIDs("bogus-x", "yo-y") < 0);
        }
Пример #2
0
        public void TestParseRevID()
        {
            var parsed = RevisionID.ParseRevId("1-utiopturoewpt");

            Assert.AreEqual(1, parsed.Item1);
            Assert.AreEqual("utiopturoewpt", parsed.Item2);

            parsed = RevisionID.ParseRevId("321-fdjfdsj-e");
            Assert.AreEqual(321, parsed.Item1);
            Assert.AreEqual("fdjfdsj-e", parsed.Item2);

            parsed = RevisionID.ParseRevId("0-fdjfdsj-e");
            Assert.IsTrue(parsed.Item1 == 0 && parsed.Item2 == "fdjfdsj-e");
            parsed = RevisionID.ParseRevId("-4-fdjfdsj-e");
            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId("5_fdjfdsj-e");
            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId(" 5-fdjfdsj-e");
            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId("7 -foo");
            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId("7-");
            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId("7");

            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId("eiuwtiu");

            Assert.IsTrue(parsed.Item1 < 0);
            parsed = RevisionID.ParseRevId(string.Empty);
            Assert.IsTrue(parsed.Item1 < 0);
        }
Пример #3
0
        /// <summary>
        /// Finds the common ancestor.
        /// </summary>
        /// <remarks>
        /// Given a revision and an array of revIDs, finds the latest common ancestor revID
        /// and returns its generation #. If there is none, returns 0.
        /// </remarks>
        /// <returns>The common ancestor.</returns>
        /// <param name="rev">Rev.</param>
        /// <param name="possibleRevIDs">Possible rev I ds.</param>
        internal static int FindCommonAncestor(RevisionInternal rev, IList <string> possibleRevIDs)
        {
            if (possibleRevIDs == null || possibleRevIDs.Count == 0)
            {
                return(0);
            }

            var history = Database.ParseCouchDBRevisionHistory(rev.GetProperties());

            Debug.Assert(history != null);

            history = history.Intersect(possibleRevIDs).ToList();

            var ancestorID = history.Count == 0
                ? null
                : history[0];

            if (ancestorID == null)
            {
                return(0);
            }

            var parsed = RevisionID.ParseRevId(ancestorID);

            return(parsed.Item1);
        }
Пример #4
0
 // Used by plugins
 internal DocumentChange(RevisionInternal addedRevision, RevisionID winningRevisionId, bool isConflict, Uri sourceUrl)
 {
     AddedRevision     = addedRevision;
     WinningRevisionId = winningRevisionId;
     IsConflict        = isConflict;
     SourceUrl         = sourceUrl;
 }
 // Used by plugins
 internal DocumentChange(RevisionInternal addedRevision, RevisionID winningRevisionId, bool isConflict, Uri sourceUrl)
 {
     AddedRevision = addedRevision;
     WinningRevisionId = winningRevisionId;
     IsConflict = isConflict;
     SourceUrl = sourceUrl;
 }
Пример #6
0
 internal RevisionInternal(string docId, RevisionID revId, bool deleted)
 {
     // TODO: get rid of this field!
     _docId  = docId;
     _revId  = revId;
     Deleted = deleted;
 }
        internal static void SetRevID(this IDictionary<string, object> dict, RevisionID revId)
        {
            if(dict == null) {
                return;
            }

            dict["_rev"] = revId.ToString();
        }
Пример #8
0
        internal static void SetRevID(this IDictionary <string, object> dict, RevisionID revId)
        {
            if (dict == null)
            {
                return;
            }

            dict["_rev"] = revId.ToString();
        }
        internal static unsafe void PinAndUse(this RevisionID revID, Action <C4Slice> action)
        {
            var data = revID.AsData();

            fixed(byte *dataPtr = data)
            {
                var slice = new C4Slice(dataPtr, (uint)data.Length);

                action(slice);
            }
        }
Пример #10
0
        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);
        }
Пример #11
0
        internal SavedRevision GetRevisionWithId(RevisionID revId, bool withBody)
        {
            if (revId == null)
            {
                return(null);
            }

            if (revId.Equals(currentRevision.Id))
            {
                return(currentRevision);
            }

            return(GetRevisionFromRev(Database.GetDocument(Id, revId, withBody)));
        }
Пример #12
0
        public RevisionInternal Copy(string docId, RevisionID revId)
        {
            System.Diagnostics.Debug.Assert((docId != null));
            System.Diagnostics.Debug.Assert(((_docId == null) || (_docId.Equals(docId))));

            var result = new RevisionInternal(docId, revId, Deleted);
            var unmodifiableProperties = GetProperties();
            var properties             = new Dictionary <string, object>();

            if (unmodifiableProperties != null)
            {
                properties.PutAll(unmodifiableProperties);
            }

            properties.SetDocRevID(docId, revId);
            result.SetProperties(properties);
            return(result);
        }
        public static RevisionID RevIDForJSON(IEnumerable<byte> json, bool deleted, RevisionID prevRevID)
        {
            // Revision IDs have a generation count, a hyphen, and a hex digest
            var generation = 0;
            if(prevRevID != null) {
                generation = prevRevID.Generation;
                if(generation == 0) {
                    return null;
                }
            }

            // Generate a digest for this revision based on the previous revision ID, document JSON,
            // and attachment digests. This doesn't need to be secure; we just need to ensure that this
            // code consistently generates the same ID given equivalent revisions.
            MessageDigest md5Digest;
            try {
                md5Digest = MessageDigest.GetInstance("MD5");
            } catch(NotSupportedException) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, Tag, "Failed to acquire a class to create MD5");
            }

            if(prevRevID != null) {
                var prevIDData = prevRevID.AsData();
                var length = prevIDData.Length;
                var lengthByte = unchecked((byte)(length & unchecked((0xFF))));
                md5Digest.Update(lengthByte);
                if(lengthByte > 0) {
                    md5Digest.Update(prevIDData);
                }
            }

            var isDeleted = deleted ? 1 : 0;
            md5Digest.Update((byte)isDeleted);

            if(json != null) {
                md5Digest.Update(json.ToArray());
            }

            var md5DigestResult = md5Digest.Digest();
            var digestAsHex = BitConverter.ToString(md5DigestResult).Replace("-", String.Empty);
            int generationIncremented = generation + 1;
            return RevisionIDFactory.FromString(String.Format("{0}-{1}", generationIncremented, digestAsHex).ToLower());
        }
Пример #14
0
        internal Body(IEnumerable <byte> json, string docID, RevisionID revID, bool deleted)
        {
            var count = json.Count();

            if (json != null && count < 2)
            {
                _jsonObject = new NonNullDictionary <string, object> {
                    { "_id", docID },
                    { "_rev", revID.ToString() },
                    { "_deleted", deleted ? (object)true : null }
                };
                return;
            }

            var stringToAdd = String.Format("{{\"_id\":\"{0}\",\"_rev\":\"{1}\",{2}}}", docID, revID,
                                            deleted ? "\"_deleted\":true," : String.Empty);
            var bytes = Encoding.UTF8.GetBytes(stringToAdd).ToList();

            bytes.InsertRange(bytes.Count - 1, json.Skip(1).Take(count - 2));
            _json = bytes.ToArray();
        }
 internal RevisionInternal(string docId, RevisionID revId, bool deleted, Body body)
     : this(docId, revId, deleted)
 {
     _body = body;
 }
Пример #16
0
 private RevisionInternal GetDocumentWithIDAndRev(string docId, RevisionID revId, bool withBody)
 {
     return Storage.GetDocument(docId, revId, withBody);
 }
Пример #17
0
 internal RevisionInternal PutRevision(RevisionInternal oldRev, RevisionID prevRevId, bool allowConflict, Uri source)
 {
     return PutDocument(oldRev.DocID, oldRev.GetProperties(), prevRevId, allowConflict, source);
 }
Пример #18
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.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());
        }
Пример #19
0
        public static ICouchbaseResponseState RevsDiff(ICouchbaseListenerContext context)
        {
            // Collect all of the input doc/revision IDs as CBL_Revisions:
            var revs = new RevisionList();
            var body = context.BodyAs <Dictionary <string, object> >();

            if (body == null)
            {
                return(context.CreateResponse(StatusCode.BadJson).AsDefaultState());
            }

            foreach (var docPair in body)
            {
                var revIDs = docPair.Value.AsList <string>();
                if (revIDs == null)
                {
                    return(context.CreateResponse(StatusCode.BadParam).AsDefaultState());
                }

                foreach (var revID in revIDs)
                {
                    var rev = new RevisionInternal(docPair.Key, revID.AsRevID(), false);
                    revs.Add(rev);
                }
            }

            return(PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                // Look them up, removing the existing ones from revs:
                db.Storage.FindMissingRevisions(revs);

                // Return the missing revs in a somewhat different format:
                IDictionary <string, object> diffs = new Dictionary <string, object>();
                foreach (var rev in revs)
                {
                    var docId = rev.DocID;
                    IList <RevisionID> missingRevs = null;
                    if (!diffs.ContainsKey(docId))
                    {
                        missingRevs = new List <RevisionID>();
                        diffs[docId] = new Dictionary <string, IList <RevisionID> > {
                            { "missing", missingRevs }
                        };
                    }
                    else
                    {
                        missingRevs = ((Dictionary <string, IList <RevisionID> >)diffs[docId])["missing"];
                    }

                    missingRevs.Add(rev.RevID);
                }

                // Add the possible ancestors for each missing revision:
                foreach (var docPair in diffs)
                {
                    IDictionary <string, IList <RevisionID> > docInfo = (IDictionary <string, IList <RevisionID> >)docPair.Value;
                    int maxGen = 0;
                    RevisionID maxRevID = null;
                    foreach (var revId in docInfo["missing"])
                    {
                        if (revId.Generation > maxGen)
                        {
                            maxGen = revId.Generation;
                            maxRevID = revId;
                        }
                    }

                    var rev = new RevisionInternal(docPair.Key, maxRevID, false);
                    var ancestors = db.Storage.GetPossibleAncestors(rev, 0, ValueTypePtr <bool> .NULL, false)?.ToList();
                    if (ancestors != null && ancestors.Count > 0)
                    {
                        docInfo["possible_ancestors"] = ancestors;
                    }
                }

                response.JsonBody = new Body(diffs);
                return response;
            }).AsDefaultState());
        }
Пример #20
0
 internal IDictionary<string, object> GetAttachmentsFromDoc(string docId, RevisionID revId)
 {
     var rev = new RevisionInternal(docId, revId, false);
     LoadRevisionBody(rev);
     return rev.GetAttachments();
 }
Пример #21
0
 internal RevisionInternal(string docId, RevisionID revId, bool deleted, Body body)
     : this(docId, revId, deleted)
 {
     _body = body;
 }
Пример #22
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.  Used by the listener;
        /// </remarks>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, RevisionID oldRevID, Uri source)
        {
            if(StringEx.IsNullOrWhiteSpace(filename)) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "Invalid filename (null or whitespace) in UpdateAttachment");
            }

            if(body != null && contentType == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "Body provided, but content type is null in UpdateAttachment");
            }

            if(oldRevID != null && docID == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "oldRevID provided ({0}) but docID is null in UpdateAttachment", oldRevID);
            }

            if(body != null && docID == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "body provided but docID is null in UpdateAttachment");
            }

            var oldRev = new RevisionInternal(docID, oldRevID, false);
            if(oldRevID != null) {
                // Load existing revision if this is a replacement:
                try {
                    oldRev = LoadRevisionBody(oldRev);
                } catch(CouchbaseLiteException e) {
                    if(e.Code == StatusCode.NotFound && GetDocument(docID, null, false) != null) {
                        throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                            "Conflict detected in UpdateAttachment");
                    }

                    Log.To.Database.E(TAG, "Error loading revision body in UpdateAttachment, rethrowing...");
                    throw;
                }
            } else {
                // If this creates a new doc, it needs a body:
                oldRev.SetBody(new Body(new Dictionary<string, object>()));
            }

            // Update the _attachments dictionary:
            var attachments = oldRev.GetProperties().Get("_attachments").AsDictionary<string, object>();
            if(attachments == null) {
                attachments = new Dictionary<string, object>();
            }

            if(body != null) {
                var key = body.GetBlobKey();
                string digest = key.Base64Digest();
                RememberAttachmentWriter(body, digest);
                string encodingName = (encoding == AttachmentEncoding.GZIP) ? "gzip" : null;
                attachments[filename] = new NonNullDictionary<string, object> {
                    { "digest", digest },
                    { "length", body.GetLength() },
                    { "follows", true },
                    { "content_type", contentType },
                    { "encoding", encodingName }
                };
            } else {
                if(oldRevID != null && attachments.Get(filename) == null) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.AttachmentNotFound, TAG,
                        "Attachment {0} not found", new SecureLogString(filename, LogMessageSensitivity.PotentiallyInsecure));
                }

                attachments.Remove(filename);
            }

            var properties = oldRev.GetProperties();
            properties["_attachments"] = attachments;
            oldRev.SetProperties(properties);

            var newRev = PutRevision(oldRev, oldRevID, false, source);
            return newRev;
        }
        public RevisionInternal PutRevision(string inDocId, RevisionID inPrevRevId, IDictionary<string, object> properties,
            bool deleting, bool allowConflict, Uri source, StoreValidation validationBlock)
        {
            if(_config.HasFlag(C4DatabaseFlags.ReadOnly)) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Forbidden, TAG,
                    "Attempting to write to a readonly database (PutRevision)");
            }

            var json = default(string);
            if(properties != null) {
                json = Manager.GetObjectMapper().WriteValueAsString(Database.StripDocumentJSON(properties), true);
            } else {
                json = "{}";
            }

            if(inDocId == null) {
                inDocId = Misc.CreateGUID();
            }

            C4Document* doc = null;
            var putRev = default(RevisionInternal);
            var change = default(DocumentChange);
            var success = RunInTransaction(() =>
            {
                try {
                    var docId = inDocId;
                    var prevRevId = inPrevRevId;
                    C4DocPutRequest rq = new C4DocPutRequest {
                        body = json,
                        docID = docId,
                        deletion = deleting,
                        hasAttachments = properties?.Get("_attachments") != null,
                        existingRevision = false,
                        allowConflict = allowConflict,
                        history = prevRevId == null ? null : new[] { prevRevId.ToString() },
                        save = false
                    };

                    UIntPtr commonAncestorIndex = UIntPtr.Zero;
                    doc = (C4Document*)ForestDBBridge.Check(err =>
                    {
                        UIntPtr tmp;
                        var retVal = Native.c4doc_put(Forest, rq, &tmp, err);
                        commonAncestorIndex = tmp;
                        return retVal;
                    });

                    if(docId == null) {
                        docId = (string)doc->docID;
                    }

                    var newRevID = doc->selectedRev.revID.AsRevID();

                    Body body = null;
                    if(properties != null) {
                        properties.SetDocRevID(docId, newRevID);
                        body = new Body(properties);
                    }

                    putRev = new RevisionInternal(docId, newRevID, deleting, body);
                    if((uint)commonAncestorIndex == 0U) {
                        return true;
                    }

                    if(validationBlock != null) {
                        var prevRev = default(RevisionInternal);
                        if(Native.c4doc_selectParentRevision(doc)) {
                            prevRev = new ForestRevisionInternal(doc, false);
                        }

                        var status = validationBlock(putRev, prevRev, prevRev == null ? null : prevRev.RevID);
                        if(status.IsError) {
                            Log.To.Validation.I(TAG, "{0} ({1}) failed validation", new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure), new SecureLogString(newRevID, LogMessageSensitivity.PotentiallyInsecure));
                            throw new CouchbaseLiteException("A document failed validation", status.Code);
                        }
                    }

                    var isWinner = SaveDocument(doc, newRevID, properties);
                    putRev.Sequence = (long)doc->sequence;
                    change = ChangeWithNewRevision(putRev, isWinner, doc, null);
                    return true;
                } finally {
                    Native.c4doc_free(doc);
                }
            });

            if(!success) {
                return null;
            }

            if(Delegate != null && change != null) {
                Delegate.DatabaseStorageChanged(change);
            }

            return putRev;
        }
        private void WithC4Document(string docId, RevisionID revId, bool withBody, bool create, C4DocumentActionDelegate block)
        {
            if(!IsOpen) {
                return;
            }

            var doc = default(C4Document*);
            try { 
                doc = (C4Document *)RetryHandler.RetryIfBusy().AllowErrors(
                    new C4Error() { code = 404, domain = C4ErrorDomain.HTTP },
                    new C4Error() { code = (int)ForestDBStatus.KeyNotFound, domain = C4ErrorDomain.ForestDB })
                    .Execute(err => Native.c4doc_get(Forest, docId, !create, err));
                if(doc != null) {
                    var selected = true;
                    if(revId != null) {
                        selected = RetryHandler.RetryIfBusy().HandleExceptions(e =>
                        {
                            if(e.Code == 404) {
                                Native.c4doc_free(doc);
                                doc = null;
                                return;
                            }

                            throw e;
                        }).AllowError(410, C4ErrorDomain.HTTP).Execute(err =>
                        {
                            bool result = false;
                            revId.PinAndUse(slice =>
                            {
                                result = Native.c4doc_selectRevision(doc, slice, withBody, err);
                            });

                            return result;
                        });
                    }

                    if(selected && withBody) {
                        RetryHandler.RetryIfBusy().AllowError(410, C4ErrorDomain.HTTP).Execute((err => Native.c4doc_loadRevisionBody(doc, err)));
                    }
                }

                block(doc);
            } finally {
                Native.c4doc_free(doc);
            }
        }
        public RevisionInternal PutLocalRevision(RevisionInternal revision, RevisionID prevRevId, bool obeyMVCC)
        {
            var docId = revision.DocID;
            if(!docId.StartsWith("_local/")) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadId, TAG,
                    "Invalid document ID ({0}) in write operation, it must start with _local/",
                    new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure));
            }

            if(revision.Deleted) {
                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 = prevRevId == null ? 0 : prevRevId.Generation;
                    if(obeyMVCC) {
                        var currentRevId = (doc != null ? doc->meta.AsRevID() : null);
                        if(prevRevId != null) {
                            if(prevRevId != currentRevId) {
                                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                                    "Attempt to write new revision on {0} of {1} when a newer revision ({2}) exists",
                                    prevRevId, new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure),
                                    currentRevId);
                            }

                            if(generation == 0) {
                                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadId, TAG,
                                    "Attempt to write new revision on invalid revision ID ({0}) for document {1}",
                                    prevRevId, new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure));
                            }
                        } else if(doc != null) {
                            throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                                "Revision ID not specified, but document {0} already exists (current rev: {1})",
                                new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure), currentRevId);
                        }
                    }

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

                return true;
            });

            return result;
        }
        public RevisionInternal GetLocalDocument(string docId, RevisionID revId)
        {
            if(!docId.StartsWith("_local/")) {
                return null;
            }

            var retVal = default(RevisionInternal);
            WithC4Raw(docId, "_local", doc =>
            {
                if(doc == null) {
                    return;
                }

                var gotRevId = doc->meta.AsRevID();
                if(revId != null && revId != gotRevId || doc->body.size == 0) {
                    return;
                }

                var properties = default(IDictionary<string, object>);
                try {
                    properties = Manager.GetObjectMapper().ReadValue<IDictionary<string, object>>(doc->body);
                } catch(CouchbaseLiteException) {
                    Log.To.Database.W(TAG, "Invalid JSON for document {0}\n{1}",
                        new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure),
                        new SecureLogString(doc->body.ToArray(), LogMessageSensitivity.PotentiallyInsecure));
                    return;
                }

                properties.SetDocRevID(docId, gotRevId);
                retVal = new RevisionInternal(docId, revId, false);
                retVal.SetProperties(properties);
            });

            return retVal;
        }
Пример #27
0
 public PulledRevision(string docId, RevisionID revId, bool deleted, Database database
                       ) : base(docId, revId, deleted)
 {
 }
Пример #28
0
        /// <summary>VALIDATION</summary>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal Status ValidateRevision(RevisionInternal newRev, RevisionInternal oldRev, RevisionID parentRevId)
        {
            var validations = Shared.GetValues("validation", Name);
            if(validations == null || validations.Count == 0) {
                return new Status(StatusCode.Ok);
            }

            var publicRev = new SavedRevision(this, newRev, parentRevId);
            var context = new ValidationContext(this, oldRev, newRev);
            Status status = new Status(StatusCode.Ok);
            foreach(var validationName in validations.Keys) {
                var validation = GetValidation(validationName);
                try {
                    validation(publicRev, context);
                } catch(Exception e) {
                    Log.To.Database.E(TAG, String.Format("Validation block '{0}' got exception, " +
                        "aborting validation process...", validationName), e);
                    status.Code = StatusCode.Exception;
                    break;
                }

                if(context.RejectMessage != null) {
                    Log.To.Validation.I(TAG, "Failed update of {0}: {1}:{2} Old doc = {3}{2} New doc = {4}", oldRev, context.RejectMessage,
                            Environment.NewLine, oldRev == null ? null : oldRev.GetProperties(), newRev.GetProperties());
                    status.Code = StatusCode.Forbidden;
                    break;
                }
            }

            return status;
        }
Пример #29
0
        internal RevisionInternal PutDocument(string docId, IDictionary<string, object> properties, RevisionID prevRevId, bool allowConflict, Uri source)
        {
            bool deleting = properties == null || properties.GetCast<bool>("_deleted");
            Log.To.Database.V(TAG, "PUT _id={0}, _rev={1}, _deleted={2}, allowConflict={3}",
                new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure), prevRevId, deleting, allowConflict);
            if(prevRevId != null && docId == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadId, TAG,
                    "prevRevId {0} specified in PutDocument, but docId not specified", prevRevId);
            }

            if(deleting && docId == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadId, TAG,
                    "No document ID specified on a delete request");
            }

            if(properties != null && properties.Get("_attachments").AsDictionary<string, object>() != null) {
                var generation = prevRevId == null ? 1 : prevRevId.Generation + 1;
                var tmpRevID = String.Format("{0}-00", generation).AsRevID();
                var tmpRev = new RevisionInternal(docId ?? "x", tmpRevID, deleting);
                tmpRev.SetProperties(properties);
                if(!ProcessAttachmentsForRevision(tmpRev, prevRevId == null ? null : new List<RevisionID> { prevRevId })) {
                    return null;
                }

                properties = tmpRev.GetProperties();
            }

            StoreValidation validationBlock = null;
            if(Shared.HasValues("validation", Name)) {
                validationBlock = ValidateRevision;
            }

            return Storage.PutRevision(docId, prevRevId, properties, deleting, allowConflict, source, validationBlock);
        }
Пример #30
0
 internal SavedRevision(Database database, RevisionInternal revision, RevisionID parentRevId)
     : this(database, revision)
 {
     _parentRevID = parentRevId;
 }
        public RevisionInternal GetDocument(string docId, RevisionID revId, bool withBody, Status outStatus = null)
        {
            if(outStatus == null) {
                outStatus = new Status();
            }

            var retVal = default(RevisionInternal);
            WithC4Document(docId, revId, withBody, false, doc =>
            {
                Log.To.Database.D(TAG, "Read {0} rev {1}", docId, revId);
                if(doc == null) {
                    outStatus.Code = StatusCode.NotFound;
                    return;
                }

                if(revId == null && doc->IsDeleted) {
                    outStatus.Code = revId == null ? StatusCode.Deleted : StatusCode.NotFound;
                    return;
                }

                outStatus.Code = StatusCode.Ok;
                retVal = new ForestRevisionInternal(doc, withBody);
            });

            return retVal;
        }
 public PulledRevision(string docId, RevisionID revId, bool deleted, Database database
     ) : base(docId, revId, deleted)
 {
 }
Пример #33
0
 private bool RevIdGreaterThanCurrent(string revId)
 {
     return(RevisionID.CBLCompareRevIDs(revId, currentRevision.Id) > 0);
 }
Пример #34
0
 internal SavedRevision GetRevisionWithId(RevisionID revId)
 {
     return(GetRevisionWithId(revId, true));
 }
 internal SavedRevision(Database database, RevisionInternal revision, RevisionID parentRevId)
     : this(database, revision)
 {
     _parentRevID = parentRevId;
 }
Пример #36
0
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal SavedRevision PutProperties(IDictionary <string, object> properties, RevisionID prevID, bool allowConflict)
        {
            var newRev = Database.PutDocument(Id, PropertiesToInsert(properties), prevID, allowConflict, null);

            if (newRev == null)
            {
                return(null);
            }

            return(GetRevisionFromRev(newRev));
        }
Пример #37
0
 //TODO: Remove this method, it only exists for tests
 internal RevisionInternal PutRevision(RevisionInternal oldRev, RevisionID prevRevId, bool allowConflict)
 {
     return PutRevision(oldRev, prevRevId, allowConflict, null);
 }
        public bool UpdateIndexes(IEnumerable <IViewStore> inputViews)
        {
            Log.To.View.I(Tag, "Checking indexes of ({0}) for {1}", ViewNames(inputViews.Cast <SqliteViewStore>()), Name);
            var db = _dbStorage;

            var status = false;

            status = db.RunInTransaction(() =>
            {
                long dbMaxSequence       = db.LastSequence;
                long forViewLastSequence = LastSequenceIndexed;

                // Check whether we need to update at all,
                // and remove obsolete emitted results from the 'maps' table:
                long minLastSequence    = dbMaxSequence;
                long[] viewLastSequence = new long[inputViews.Count()];
                int deletedCount        = 0;
                int i = 0;
                HashSet <string> docTypes = new HashSet <string>();
                IDictionary <string, string> viewDocTypes = null;
                bool allDocTypes = false;
                IDictionary <int, int> viewTotalRows = new Dictionary <int, int>();
                List <SqliteViewStore> views         = new List <SqliteViewStore>(inputViews.Count());
                List <MapDelegate> mapBlocks         = new List <MapDelegate>();
                foreach (var view in inputViews.Cast <SqliteViewStore>())
                {
                    var viewDelegate = view.Delegate;
                    var mapBlock     = viewDelegate == null ? null : viewDelegate.Map;
                    if (mapBlock == null)
                    {
                        Debug.Assert(view != this, String.Format("Cannot index view {0}: no map block registered", view.Name));
                        Log.To.View.V(Tag, "    {0} has no map block; skipping it", view.Name);
                        continue;
                    }

                    long last = view == this ? forViewLastSequence : view.LastSequenceIndexed;
                    if (last >= dbMaxSequence)
                    {
                        Log.To.View.V(Tag, "{0} is already up to date, skipping...", view.Name);
                        continue;
                    }

                    views.Add(view);
                    mapBlocks.Add(mapBlock);

                    int viewId = view.ViewID;
                    Debug.Assert(viewId > 0, String.Format("View '{0}' not found in database", view.Name));

                    int totalRows         = view.TotalRows;
                    viewTotalRows[viewId] = totalRows;


                    viewLastSequence[i++] = last;
                    if (last < 0)
                    {
                        throw Misc.CreateExceptionAndLog(Log.To.View, StatusCode.DbError, Tag,
                                                         "Invalid last sequence indexed ({0}) received from {1}", last, view);
                    }

                    if (last < dbMaxSequence)
                    {
                        if (last == 0)
                        {
                            CreateIndex();
                        }

                        minLastSequence = Math.Min(minLastSequence, last);
                        Log.To.View.V(Tag, "    {0} last indexed at #{1}", view.Name, last);

                        string docType = viewDelegate.DocumentType;
                        if (docType != null)
                        {
                            docTypes.Add(docType);
                            if (viewDocTypes == null)
                            {
                                viewDocTypes = new Dictionary <string, string>();
                            }

                            viewDocTypes[view.Name] = docType;
                        }
                        else
                        {
                            // can't filter by doc_type
                            allDocTypes = true;
                        }

                        bool ok     = true;
                        int changes = 0;
                        if (last == 0)
                        {
                            try {
                                // If the lastSequence has been reset to 0, make sure to remove all map results:
                                using (var changesCursor = db.StorageEngine.RawQuery(view.QueryString("SELECT COUNT(*) FROM maps_#"))) {
                                    changes = changesCursor.GetInt(0);
                                }

                                DeleteIndex();
                                CreateIndex();
                            } catch (Exception) {
                                ok = false;
                            }
                        }
                        else
                        {
                            db.OptimizeSQLIndexes();     // ensures query will use the right indexes
                            // Delete all obsolete map results (ones from since-replaced revisions):
                            try {
                                changes = db.StorageEngine.ExecSQL(view.QueryString("DELETE FROM 'maps_#' WHERE sequence IN (" +
                                                                                    "SELECT parent FROM revs WHERE sequence>?" +
                                                                                    "AND +parent>0 AND +parent<=?)"), last, last);
                            } catch (Exception) {
                                ok = false;
                            }
                        }

                        if (!ok)
                        {
                            throw Misc.CreateExceptionAndLog(Log.To.View, StatusCode.DbError, Tag,
                                                             "Error deleting obsolete map results before index update");
                        }

                        // Update #deleted rows
                        deletedCount += changes;

                        // Only count these deletes as changes if this isn't a view reset to 0
                        if (last != 0)
                        {
                            viewTotalRows[viewId] -= changes;
                        }
                    }
                }

                if (minLastSequence == dbMaxSequence)
                {
                    return(true);
                }

                Log.To.View.I(Tag, "Updating indexes of ({0}) from #{1} to #{2} ...",
                              ViewNames(views), minLastSequence, dbMaxSequence);

                // This is the emit() block, which gets called from within the user-defined map() block
                // that's called down below.
                SqliteViewStore currentView             = null;
                IDictionary <string, object> currentDoc = null;
                long sequence     = minLastSequence;
                Status emitStatus = new Status(StatusCode.Ok);
                int insertedCount = 0;
                EmitDelegate emit = (key, value) =>
                {
                    if (key == null)
                    {
                        Log.To.View.W(Tag, "Emit function called with a null key; ignoring");
                        return;
                    }

                    StatusCode s = currentView.Emit(key, value, value == currentDoc, sequence);
                    if (s != StatusCode.Ok)
                    {
                        emitStatus.Code = s;
                    }
                    else
                    {
                        viewTotalRows[currentView.ViewID] += 1;
                        insertedCount++;
                    }
                };

                // Now scan every revision added since the last time the views were indexed:
                bool checkDocTypes = docTypes.Count > 1 || (allDocTypes && docTypes.Count > 0);
                var sql            = new StringBuilder("SELECT revs.doc_id, sequence, docid, revid, json, deleted ");
                if (checkDocTypes)
                {
                    sql.Append(", doc_type ");
                }

                sql.Append("FROM revs, docs WHERE sequence>? AND sequence <=? AND current!=0 ");
                if (minLastSequence == 0)
                {
                    sql.Append("AND deleted=0 ");
                }

                if (!allDocTypes && docTypes.Count > 0)
                {
                    sql.AppendFormat("AND doc_type IN ({0}) ", Utility.JoinQuoted(docTypes));
                }

                sql.Append("AND revs.doc_id = docs.doc_id " +
                           "ORDER BY revs.doc_id, deleted, revid DESC");

                Cursor c  = null;
                Cursor c2 = null;
                try {
                    c = db.StorageEngine.IntransactionRawQuery(sql.ToString(), minLastSequence, dbMaxSequence);
                    bool keepGoing = c.MoveToNext();
                    while (keepGoing)
                    {
                        // Get row values now, before the code below advances 'c':
                        long doc_id  = c.GetLong(0);
                        sequence     = c.GetLong(1);
                        string docId = c.GetString(2);
                        if (docId.StartsWith("_design/"))       // design documents don't get indexed
                        {
                            keepGoing = c.MoveToNext();
                            continue;
                        }

                        string revId   = c.GetString(3);
                        var json       = c.GetBlob(4);
                        bool deleted   = c.GetInt(5) != 0;
                        string docType = checkDocTypes ? c.GetString(6) : null;

                        // Skip rows with the same doc_id -- these are losing conflicts.
                        var conflicts = default(List <string>);
                        while ((keepGoing = c.MoveToNext()) && c.GetLong(0) == doc_id)
                        {
                            if (conflicts == null)
                            {
                                conflicts = new List <string>();
                            }

                            conflicts.Add(c.GetString(3));
                        }

                        long realSequence = sequence;     // because sequence may be changed, below
                        if (minLastSequence > 0)
                        {
                            // Find conflicts with documents from previous indexings.
                            using (c2 = db.StorageEngine.IntransactionRawQuery("SELECT revid, sequence FROM revs " +
                                                                               "WHERE doc_id=? AND sequence<=? AND current!=0 AND deleted=0 " +
                                                                               "ORDER BY revID DESC ", doc_id, minLastSequence)) {
                                if (c2.MoveToNext())
                                {
                                    string oldRevId = c2.GetString(0);
                                    // This is the revision that used to be the 'winner'.
                                    // Remove its emitted rows:
                                    long oldSequence = c2.GetLong(1);
                                    foreach (var view in views)
                                    {
                                        int changes   = db.StorageEngine.ExecSQL(QueryString("DELETE FROM 'maps_#' WHERE sequence=?"), oldSequence);
                                        deletedCount += changes;
                                        viewTotalRows[view.ViewID] -= changes;
                                    }

                                    if (deleted || RevisionID.CBLCompareRevIDs(oldRevId, revId) > 0)
                                    {
                                        // It still 'wins' the conflict, so it's the one that
                                        // should be mapped [again], not the current revision!
                                        revId    = oldRevId;
                                        deleted  = false;
                                        sequence = oldSequence;
                                        json     = db.QueryOrDefault <byte[]>(x => x.GetBlob(0), true, null, "SELECT json FROM revs WHERE sequence=?", sequence);
                                    }

                                    if (!deleted)
                                    {
                                        // Conflict revisions:
                                        if (conflicts == null)
                                        {
                                            conflicts = new List <string>();
                                        }

                                        conflicts.Add(oldRevId);
                                        while (c2.MoveToNext())
                                        {
                                            conflicts.Add(c2.GetString(0));
                                        }
                                    }
                                }
                            }
                        }

                        if (deleted)
                        {
                            continue;
                        }

                        // Get the document properties, to pass to the map function:
                        currentDoc = db.GetDocumentProperties(json, docId, revId, deleted, sequence);
                        if (currentDoc == null)
                        {
                            Log.To.View.W(Tag, "Failed to parse JSON of doc {0} rev {1}, skipping...",
                                          new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure), revId);
                            continue;
                        }

                        currentDoc["_local_seq"] = sequence;
                        if (conflicts != null)
                        {
                            currentDoc["_conflicts"] = conflicts;
                        }

                        // Call the user-defined map() to emit new key/value pairs from this revision:
                        int viewIndex = -1;
                        var e         = views.GetEnumerator();
                        while (e.MoveNext())
                        {
                            currentView = e.Current;
                            ++viewIndex;
                            if (viewLastSequence[viewIndex] < realSequence)
                            {
                                if (checkDocTypes)
                                {
                                    var viewDocType = viewDocTypes[currentView.Name];
                                    if (viewDocType != null && viewDocType != docType)
                                    {
                                        // skip; view's documentType doesn't match this doc
                                        continue;
                                    }
                                }

                                Log.To.View.V(Tag, "    #{0}: map \"{1}\" for view {2}...",
                                              sequence, docId, e.Current.Name);
                                try {
                                    mapBlocks[viewIndex](currentDoc, emit);
                                } catch (Exception x) {
                                    Log.To.View.E(Tag, String.Format("Exception in map() block for view {0}, cancelling update...", currentView.Name), x);
                                    emitStatus.Code = StatusCode.Exception;
                                }

                                if (emitStatus.IsError)
                                {
                                    c.Dispose();
                                    return(false);
                                }
                            }
                        }

                        currentView = null;
                    }
                } catch (CouchbaseLiteException) {
                    Log.To.View.E(Tag, "Failed to update index for {0}, rethrowing...", currentView.Name);
                    throw;
                } catch (Exception e) {
                    throw Misc.CreateExceptionAndLog(Log.To.View, e, Tag, "Error updating index for {0}", currentView.Name);
                } finally {
                    if (c != null)
                    {
                        c.Dispose();
                    }
                }

                // Finally, record the last revision sequence number that was indexed and update #rows:
                foreach (var view in views)
                {
                    view.FinishCreatingIndex();
                    int newTotalRows = viewTotalRows[view.ViewID];
                    Debug.Assert(newTotalRows >= 0);

                    var args             = new ContentValues();
                    args["lastSequence"] = dbMaxSequence;
                    args["total_docs"]   = newTotalRows;
                    try {
                        db.StorageEngine.Update("views", args, "view_id=?", view.ViewID.ToString());
                    } catch (CouchbaseLiteException) {
                        Log.To.View.E(Tag, "Failed to update view {0}, rethrowing...", view.Name);
                        throw;
                    } catch (Exception e) {
                        throw Misc.CreateExceptionAndLog(Log.To.View, e, Tag, "Error updating view {0}", view.Name);
                    }
                }

                Log.To.View.I(Tag, "...Finished re-indexing ({0}) to #{1} (deleted {2}, added {3})",
                              ViewNames(views), dbMaxSequence, deletedCount, insertedCount);
                return(true);
            });

            if (!status)
            {
                Log.To.View.W(Tag, "Failed to rebuild views ({0}): {1}", ViewNames(inputViews.Cast <SqliteViewStore>()), status);
            }

            return(status);
        }
        public RevisionInternal Copy(string docId, RevisionID revId)
        {
            System.Diagnostics.Debug.Assert((docId != null));
            System.Diagnostics.Debug.Assert(((_docId == null) || (_docId.Equals(docId))));

            var result = new RevisionInternal(docId, revId, Deleted);
            var unmodifiableProperties = GetProperties();
            var properties = new Dictionary<string, object>();
            if(unmodifiableProperties != null) {
                properties.PutAll(unmodifiableProperties);
            }

            properties.SetDocRevID(docId, revId);
            result.SetProperties(properties);
            return result;
        }
 public int Compare(RevisionID x, RevisionID y)
 {
     return x.CompareTo(y);
 }
        public override int CompareTo(RevisionID other)
        {
            var otherCast = other as TreeRevisionID;
            if(otherCast == null) {
                Log.To.Database.E(Tag, "Cannot compare to {0}, throwing...", other.GetType().Name);
                throw new ArgumentException(String.Format("Cannot compare TreeRevisionID to {0}", other.GetType().Name));
            }

            return CBLCollateRevIDs(_data, otherCast._data);
        }
        private void DeleteLocalRevision(string docId, RevisionID revId, bool obeyMVCC)
        {
            if(!docId.StartsWith("_local/")) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadId, TAG,
                    "Local revision IDs must start with _local/");
            }

            if(obeyMVCC && revId == null) {
                // Didn't specify a revision to delete: NotFound or a Conflict, depending
                var gotLocalDoc = GetLocalDocument(docId, null);
                if(gotLocalDoc == null) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.NotFound, TAG,
                        "No revision ID specified in local delete operation");
                }

                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                    "No revision ID specified in local delete operation");
            }

            RunInTransaction(() =>
            {
                WithC4Raw(docId, "_local", doc =>
                {
                    if(doc == null) {
                        throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.NotFound, TAG,
                            "Specified revision ({0}) in delete operation not found", revId);
                    }

                    var currentRevID = doc->meta.AsRevID();
                    if(obeyMVCC && (revId != currentRevID)) {
                        throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                            "Specified revision ({0}) in delete operation != current revision ({1})", revId, currentRevID);
                    }

                    ForestDBBridge.Check(err => Native.c4raw_put(Forest, "_local", docId, null, null, err));
                });
                return true;
            });
        }
 internal RevisionInternal(string docId, RevisionID revId, bool deleted)
 {
     // TODO: get rid of this field!
     _docId = docId;
     _revId = revId;
     Deleted = deleted;
 }
        private bool SaveDocument(C4Document* doc, RevisionID revId, IDictionary<string, object> properties)
        {
            // Is the new revision the winner?
            var winningRevID = doc->revID.AsRevID();
            bool isWinner = winningRevID.Equals(revId);

            // Update the documentType:
            if(!isWinner) {
                Native.c4doc_selectCurrentRevision(doc);
                properties = Manager.GetObjectMapper().ReadValue<IDictionary<string, object>>(doc->selectedRev.body);
            }

            Native.c4doc_setType(doc, properties?.GetCast<string>("type"));
            // Save:
            ForestDBBridge.Check(err => Native.c4doc_save(doc, (uint)MaxRevTreeDepth, err));
            return isWinner;
        }
Пример #45
0
 internal RevisionInternal GetDocument(string docId, RevisionID revId, bool withBody, Status outStatus = null)
 {
     return Storage.GetDocument(docId, revId, withBody, outStatus);
 }