示例#1
0
        public IDictionary <string, object> GetProperties()
        {
            IDictionary <string, object> result = null;

            if (_body != null)
            {
                try {
                    result = _body.GetProperties();
                } catch (InvalidOperationException) {
                    // handle when both object and json are null for this body
                    return(null);
                }

                if (_docId != null)
                {
                    result["_id"] = _docId;
                }

                if (_revId != null)
                {
                    result.SetRevID(_revId);
                }

                if (Deleted)
                {
                    result["_deleted"] = true;
                }
            }
            return(result);
        }
示例#2
0
        internal IDictionary <String, Object> GetProperties()
        {
            IDictionary <string, object> result = null;

            if (_body != null)
            {
                IDictionary <string, object> prop;
                try {
                    prop = _body.GetProperties();
                } catch (InvalidOperationException) {
                    // handle when both object and json are null for this body
                    return(null);
                }

                if (result == null)
                {
                    result = new Dictionary <string, object>();
                }
                result.PutAll(prop);

                if (_docId != null)
                {
                    result["_id"] = _docId;
                }

                if (_revId != null)
                {
                    result["_rev"] = _revId;
                }

                if (_deleted)
                {
                    result["_deleted"] = true;
                }
            }
            return(result);
        }
示例#3
0
        public virtual IDictionary <string, object> GetProperties()
        {
            IDictionary <string, object> result = null;

            if (body != null)
            {
                IDictionary <string, object> prop;
                try
                {
                    prop = body.GetProperties();
                }
                catch (InvalidOperationException)
                {
                    // handle when both object and json are null for this body
                    return(null);
                }
                if (result == null)
                {
                    result = new Dictionary <string, object>();
                }
                result.PutAll(prop);
            }
            return(result);
        }
示例#4
0
 internal RevisionInternal(Body body)
     : this(body.GetProperties().CblID(), body.GetProperties().CblRev(), body.GetProperties().CblDeleted())
 {
     _body = body;
 }
        /// <summary>
        /// Creates (and executes) a temporary view based on the view function supplied in the JSON request.
        /// </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/database/temp-views.html#post--db-_temp_view
        /// </remarks>
        public static ICouchbaseResponseState ExecuteTemporaryViewFunction(ICouchbaseListenerContext context)
        {
            var response = context.CreateResponse();
            if (context.RequestHeaders["Content-Type"] == null || 
                !context.RequestHeaders["Content-Type"].StartsWith("application/json")) {
                response.InternalStatus = StatusCode.UnsupportedType;
                return response.AsDefaultState();
            }

            IEnumerable<byte> json = context.BodyStream.ReadAllBytes();
            var requestBody = new Body(json);
            if (!requestBody.IsValidJSON()) {
                response.InternalStatus = StatusCode.BadJson;
                return response.AsDefaultState();
            }

            var props = requestBody.GetProperties();
            if (props == null) {
                response.InternalStatus = StatusCode.BadJson;
                return response.AsDefaultState();
            }

            var options = context.QueryOptions;
            if (options == null) {
                response.InternalStatus = StatusCode.BadRequest;
                return response.AsDefaultState();
            }

            return PerformLogicWithDatabase(context, true, db =>
            {
                if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) {
                    response.InternalStatus = StatusCode.NotModified;
                    return response;
                }

                var view = db.GetView("@@TEMPVIEW@@");
                var status = view.Compile(props, "javascript");
                if(status.IsError) {
                    response.InternalStatus = status.Code;
                    return response;
                }

                try {
                    view.UpdateIndex_Internal();
                    return QueryView(context, null, view, options);
                } catch(CouchbaseLiteException e) {
                    response.InternalStatus = e.CBLStatus.Code;
                }

                return response;
            }).AsDefaultState();

        }
        public void TestLocalDocs()
        {
            //create a document
            var documentProperties = new Dictionary<string, object>();
            documentProperties["_id"] = "_local/doc1";
            documentProperties["foo"] = 1;
            documentProperties["bar"] = false;
            var body = new Body(documentProperties);
            var rev1 = new RevisionInternal(body);
            rev1 = database.Storage.PutLocalRevision(rev1, null, true);
            Log.V(Tag, "Created " + rev1);
            Assert.AreEqual("_local/doc1", rev1.GetDocId());
            Assert.IsTrue(rev1.GetRevId().StartsWith("1-"));

            //read it back
            var readRev = database.Storage.GetLocalDocument(rev1.GetDocId(), null);
            Assert.IsNotNull(readRev);
            var readRevProps = readRev.GetProperties();
            Assert.AreEqual(rev1.GetDocId(), readRevProps.Get("_id"));
            Assert.AreEqual(rev1.GetRevId(), readRevProps.Get("_rev"));
            AssertPropertiesAreEqual(UserProperties(readRevProps), 
                UserProperties(body.GetProperties()));

            //now update it
            documentProperties = (Dictionary<string, object>)readRev.GetProperties();
            documentProperties["status"] = "updated!";
            body = new Body(documentProperties);
            var rev2 = new RevisionInternal(body);
            var rev2input = rev2;
            rev2 = database.Storage.PutLocalRevision(rev2, rev1.GetRevId(), true);
            Log.V(Tag, "Updated " + rev1);
            Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId());
            Assert.IsTrue(rev2.GetRevId().StartsWith("2-"));
            
            //read it back
            readRev = database.Storage.GetLocalDocument(rev2.GetDocId(), null);
            Assert.IsNotNull(readRev);
            AssertPropertiesAreEqual(UserProperties(readRev.GetProperties()), 
                UserProperties(body.GetProperties()));

            // Try to update the first rev, which should fail:
            var gotException = false;
            try
            {
                database.Storage.PutLocalRevision(rev2input, rev1.GetRevId(), true);
            }
            catch (CouchbaseLiteException e)
            {
                Assert.AreEqual(StatusCode.Conflict, e.CBLStatus.Code);
                gotException = true;
            }
            Assert.IsTrue(gotException);
            
            // Delete it:
            var revD = new RevisionInternal(rev2.GetDocId(), null, true);
            gotException = false;
            try
            {
                var revResult = database.Storage.PutLocalRevision(revD, null, true);
                Assert.IsNull(revResult);
            }
            catch (CouchbaseLiteException e)
            {
                Assert.AreEqual(StatusCode.Conflict, e.CBLStatus.Code);
                gotException = true;
            }
            Assert.IsTrue(gotException);
            revD = database.Storage.PutLocalRevision(revD, rev2.GetRevId(), true);
            
            // Delete nonexistent doc:
            gotException = false;
            var revFake = new RevisionInternal("_local/fake", null, true);
            try
            {
                database.Storage.PutLocalRevision(revFake, null, true);
            }
            catch (CouchbaseLiteException e)
            {
                Assert.AreEqual(StatusCode.NotFound, e.CBLStatus.Code);
                gotException = true;
            }
            Assert.IsTrue(gotException);
            
            // Read it back (should fail):
            readRev = database.Storage.GetLocalDocument(revD.GetDocId(), null);
            Assert.IsNull(readRev);
        }
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
 public virtual void TestLocalDocs()
 {
     //create a document
     IDictionary<string, object> documentProperties = new Dictionary<string, object>();
     documentProperties.Put("_id", "_local/doc1");
     documentProperties.Put("foo", 1);
     documentProperties.Put("bar", false);
     Body body = new Body(documentProperties);
     RevisionInternal rev1 = new RevisionInternal(body, database);
     Status status = new Status();
     rev1 = database.PutLocalRevision(rev1, null);
     Log.V(Tag, "Created " + rev1);
     NUnit.Framework.Assert.AreEqual("_local/doc1", rev1.GetDocId());
     NUnit.Framework.Assert.IsTrue(rev1.GetRevId().StartsWith("1-"));
     //read it back
     RevisionInternal readRev = database.GetLocalDocument(rev1.GetDocId(), null);
     NUnit.Framework.Assert.IsNotNull(readRev);
     IDictionary<string, object> readRevProps = readRev.GetProperties();
     NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), readRev.GetProperties().Get("_id"
         ));
     NUnit.Framework.Assert.AreEqual(rev1.GetRevId(), readRev.GetProperties().Get("_rev"
         ));
     NUnit.Framework.Assert.AreEqual(UserProperties(readRevProps), UserProperties(body
         .GetProperties()));
     //now update it
     documentProperties = readRev.GetProperties();
     documentProperties.Put("status", "updated!");
     body = new Body(documentProperties);
     RevisionInternal rev2 = new RevisionInternal(body, database);
     RevisionInternal rev2input = rev2;
     rev2 = database.PutLocalRevision(rev2, rev1.GetRevId());
     Log.V(Tag, "Updated " + rev1);
     NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId());
     NUnit.Framework.Assert.IsTrue(rev2.GetRevId().StartsWith("2-"));
     //read it back
     readRev = database.GetLocalDocument(rev2.GetDocId(), null);
     NUnit.Framework.Assert.IsNotNull(readRev);
     NUnit.Framework.Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties
         (body.GetProperties()));
     // Try to update the first rev, which should fail:
     bool gotException = false;
     try
     {
         database.PutLocalRevision(rev2input, rev1.GetRevId());
     }
     catch (CouchbaseLiteException e)
     {
         NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode());
         gotException = true;
     }
     NUnit.Framework.Assert.IsTrue(gotException);
     // Delete it:
     RevisionInternal revD = new RevisionInternal(rev2.GetDocId(), null, true, database
         );
     gotException = false;
     try
     {
         RevisionInternal revResult = database.PutLocalRevision(revD, null);
         NUnit.Framework.Assert.IsNull(revResult);
     }
     catch (CouchbaseLiteException e)
     {
         NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode());
         gotException = true;
     }
     NUnit.Framework.Assert.IsTrue(gotException);
     revD = database.PutLocalRevision(revD, rev2.GetRevId());
     // Delete nonexistent doc:
     gotException = false;
     RevisionInternal revFake = new RevisionInternal("_local/fake", null, true, database
         );
     try
     {
         database.PutLocalRevision(revFake, null);
     }
     catch (CouchbaseLiteException e)
     {
         NUnit.Framework.Assert.AreEqual(Status.NotFound, e.GetCBLStatus().GetCode());
         gotException = true;
     }
     NUnit.Framework.Assert.IsTrue(gotException);
     // Read it back (should fail):
     readRev = database.GetLocalDocument(revD.GetDocId(), null);
     NUnit.Framework.Assert.IsNull(readRev);
 }
        // Perform a document operation on the specified database
        private static CouchbaseLiteResponse UpdateDb(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting)
        {
            var response = context.CreateResponse();
            if (docId != null) {
                // On PUT/DELETE, get revision ID from either ?rev= query, If-Match: header, or doc body:
                string revParam = context.GetQueryParam("rev");
                string ifMatch = context.RequestHeaders["If-Match"];
                if (ifMatch != null) {
                    if (revParam == null) {
                        revParam = ifMatch;
                    } else if (!revParam.Equals(ifMatch)) {
                        return context.CreateResponse(StatusCode.BadRequest);
                    }
                }

                if (revParam != null && body != null) {
                    var revProp = body.GetPropertyForKey("_rev");
                    if (revProp == null) {
                        // No _rev property in body, so use ?rev= query param instead:
                        var props = body.GetProperties();
                        props["_rev"] = revParam;
                        body = new Body(props);
                    } else if (!revProp.Equals(revParam)) {
                        return context.CreateResponse(StatusCode.BadRequest); // mismatch between _rev and rev
                    }
                }
            }

            RevisionInternal rev;
            StatusCode status = UpdateDocument(context, db, docId, body, deleting, false, out rev);
            if ((int)status < 300) {
                context.CacheWithEtag(rev.GetRevId()); // set ETag
                if (!deleting) {
                    var url = context.RequestUrl;
                    if (docId != null) {
                        response["Location"] = url.AbsoluteUri;
                    }
                }

                response.JsonBody = new Body(new Dictionary<string, object> {
                    { "ok", true },
                    { "id", rev.GetDocId() },
                    { "rev", rev.GetRevId() }
                });
            }

            response.InternalStatus = status;
            return response;
        }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.GetCast<string>("_id");
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            StatusCode status = StatusCode.Created;
            try {
                if (docId != null && docId.StartsWith("_local")) {
                    outRev = db.Storage.PutLocalRevision(rev, prevRevId, true); //TODO: Doesn't match iOS
                } else {
                    Status retStatus = new Status();
                    outRev = db.PutRevision(rev, prevRevId, allowConflict, retStatus);
                    status = retStatus.Code;
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }
        public void TestCRUDOperations()
        {
            database.Changed += (sender, e) => {
                var changes = e.Changes.ToList();
                foreach (DocumentChange change in changes)
                {
                    var rev = change.AddedRevision;
                    Assert.IsNotNull(rev);
                    Assert.IsNotNull(rev.GetDocId());
                    Assert.IsNotNull(rev.GetRevId());
                    Assert.AreEqual(rev.GetDocId(), rev.GetProperties()["_id"]);
                    Assert.AreEqual(rev.GetRevId(), rev.GetProperties()["_rev"]);
                }
            };

            var privateUUID = database.PrivateUUID();
            var publicUUID = database.PublicUUID();
            Log.V(Tag, "DB private UUID = '" + privateUUID + "', public UUID = '" + 
                publicUUID + "'");
            Assert.IsTrue(privateUUID.Length >= 20);
            Assert.IsTrue(publicUUID.Length >= 20);

            //create a document
            var documentProperties = new Dictionary<string, object>();
            documentProperties["foo"] = 1;
            documentProperties["bar"] = false;
            documentProperties["baz"] = "touch";

            var body = new Body(documentProperties);
            var rev1 = new RevisionInternal(body);

            rev1 = database.PutRevision(rev1, null, false);
            Log.V(Tag, "Created " + rev1);
            Assert.IsTrue(rev1.GetDocId().Length >= 10);
            Assert.IsTrue(rev1.GetRevId().StartsWith("1-"));

            //read it back
            var readRev = database.GetDocument(rev1.GetDocId(), null, 
                true);
            Assert.IsNotNull(readRev);

            var userReadRevProps = UserProperties(readRev.GetProperties());
            var userBodyProps = UserProperties(body.GetProperties());
            Assert.AreEqual(userReadRevProps.Count, userBodyProps.Count);
            foreach(var key in userReadRevProps.Keys) 
            {
                Assert.AreEqual(userReadRevProps[key], userBodyProps[key]);
            }

            //now update it
            documentProperties = (Dictionary<string, object>)readRev.GetProperties();
            documentProperties["status"] = "updated!";
            body = new Body(documentProperties);
            var rev2 = new RevisionInternal(body);
            var rev2input = rev2;
            rev2 = database.PutRevision(rev2, rev1.GetRevId(), false);
            Log.V(Tag, "Updated " + rev1);
            Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId());
            Assert.IsTrue(rev2.GetRevId().StartsWith("2-"));

            //read it back
            readRev = database.GetDocument(rev2.GetDocId(), null, 
                true);
            Assert.IsNotNull(readRev);
            Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties
                (body.GetProperties()));

            // Try to update the first rev, which should fail:
            var ex = Assert.Throws<CouchbaseLiteException>(() => database.PutRevision(rev2input, rev1.GetRevId(), false));
            Assert.AreEqual(StatusCode.Conflict, ex.Code);

            // Check the changes feed, with and without filters:
            var changeRevisions = database.ChangesSince(0, ChangesOptions.Default, null, null);

            Log.V(Tag, "Changes = " + changeRevisions);
            Assert.AreEqual(1, changeRevisions.Count);

            changeRevisions = database.ChangesSince(0, ChangesOptions.Default, 
                (revision, items) => "updated!".Equals (revision.Properties.Get("status")), null);
            Assert.AreEqual(1, changeRevisions.Count);

            changeRevisions = database.ChangesSince(0, ChangesOptions.Default, 
                (revision, items) => "not updated!".Equals (revision.Properties.Get("status")), null);
            Assert.AreEqual(0, changeRevisions.Count);

            // Delete it:
            var revD = new RevisionInternal(rev2.GetDocId(), null, true);
            ex = Assert.Throws<CouchbaseLiteException>(() => database.PutRevision(revD, null, false));
            Assert.AreEqual(StatusCode.Conflict, ex.Code);

            revD = database.PutRevision(revD, rev2.GetRevId(), false);
            Assert.AreEqual(revD.GetDocId(), rev2.GetDocId());
            Assert.IsTrue(revD.GetRevId().StartsWith("3-"));
            
            // Delete nonexistent doc:
            var revFake = new RevisionInternal("fake", null, true);
            ex = Assert.Throws<CouchbaseLiteException>(() => database.PutRevision(revFake, null, false));
            Assert.AreEqual(StatusCode.NotFound, ex.Code);

            // Read it back (should fail):
            readRev = database.GetDocument(revD.GetDocId(), null, 
                true);
            Assert.IsNull(readRev);

            // Get Changes feed:
            changeRevisions = database.ChangesSince(0, ChangesOptions.Default, null, null);
            Assert.IsTrue(changeRevisions.Count == 1);

            // Get Revision History:
            IList<RevisionInternal> history = database.Storage.GetRevisionHistory(revD, null);
            Assert.AreEqual(revD, history[0]);
            Assert.AreEqual(rev2, history[1]);
            Assert.AreEqual(rev1, history[2]);
        }
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
 public virtual void TestCRUDOperations()
 {
     database.AddChangeListener(this);
     string privateUUID = database.PrivateUUID();
     string publicUUID = database.PublicUUID();
     Log.V(Tag, "DB private UUID = '" + privateUUID + "', public UUID = '" + publicUUID
          + "'");
     NUnit.Framework.Assert.IsTrue(privateUUID.Length >= 20);
     NUnit.Framework.Assert.IsTrue(publicUUID.Length >= 20);
     //create a document
     IDictionary<string, object> documentProperties = new Dictionary<string, object>();
     documentProperties.Put("foo", 1);
     documentProperties.Put("bar", false);
     documentProperties.Put("baz", "touch");
     Body body = new Body(documentProperties);
     RevisionInternal rev1 = new RevisionInternal(body, database);
     Status status = new Status();
     rev1 = database.PutRevision(rev1, null, false, status);
     Log.V(Tag, "Created " + rev1);
     NUnit.Framework.Assert.IsTrue(rev1.GetDocId().Length >= 10);
     NUnit.Framework.Assert.IsTrue(rev1.GetRevId().StartsWith("1-"));
     //read it back
     RevisionInternal readRev = database.GetDocumentWithIDAndRev(rev1.GetDocId(), null
         , EnumSet.NoneOf<Database.TDContentOptions>());
     NUnit.Framework.Assert.IsNotNull(readRev);
     IDictionary<string, object> readRevProps = readRev.GetProperties();
     NUnit.Framework.Assert.AreEqual(UserProperties(readRevProps), UserProperties(body
         .GetProperties()));
     //now update it
     documentProperties = readRev.GetProperties();
     documentProperties.Put("status", "updated!");
     body = new Body(documentProperties);
     RevisionInternal rev2 = new RevisionInternal(body, database);
     RevisionInternal rev2input = rev2;
     rev2 = database.PutRevision(rev2, rev1.GetRevId(), false, status);
     Log.V(Tag, "Updated " + rev1);
     NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId());
     NUnit.Framework.Assert.IsTrue(rev2.GetRevId().StartsWith("2-"));
     //read it back
     readRev = database.GetDocumentWithIDAndRev(rev2.GetDocId(), null, EnumSet.NoneOf<
         Database.TDContentOptions>());
     NUnit.Framework.Assert.IsNotNull(readRev);
     NUnit.Framework.Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties
         (body.GetProperties()));
     // Try to update the first rev, which should fail:
     bool gotExpectedError = false;
     try
     {
         database.PutRevision(rev2input, rev1.GetRevId(), false, status);
     }
     catch (CouchbaseLiteException e)
     {
         gotExpectedError = e.GetCBLStatus().GetCode() == Status.Conflict;
     }
     NUnit.Framework.Assert.IsTrue(gotExpectedError);
     // Check the changes feed, with and without filters:
     RevisionList changes = database.ChangesSince(0, null, null);
     Log.V(Tag, "Changes = " + changes);
     NUnit.Framework.Assert.AreEqual(1, changes.Count);
     changes = database.ChangesSince(0, null, new _ReplicationFilter_95());
     NUnit.Framework.Assert.AreEqual(1, changes.Count);
     changes = database.ChangesSince(0, null, new _ReplicationFilter_105());
     NUnit.Framework.Assert.AreEqual(0, changes.Count);
     // Delete it:
     RevisionInternal revD = new RevisionInternal(rev2.GetDocId(), null, true, database
         );
     RevisionInternal revResult = null;
     gotExpectedError = false;
     try
     {
         revResult = database.PutRevision(revD, null, false, status);
     }
     catch (CouchbaseLiteException e)
     {
         gotExpectedError = e.GetCBLStatus().GetCode() == Status.Conflict;
     }
     NUnit.Framework.Assert.IsTrue(gotExpectedError);
     NUnit.Framework.Assert.IsNull(revResult);
     revD = database.PutRevision(revD, rev2.GetRevId(), false, status);
     NUnit.Framework.Assert.AreEqual(Status.Ok, status.GetCode());
     NUnit.Framework.Assert.AreEqual(revD.GetDocId(), rev2.GetDocId());
     NUnit.Framework.Assert.IsTrue(revD.GetRevId().StartsWith("3-"));
     // Delete nonexistent doc:
     RevisionInternal revFake = new RevisionInternal("fake", null, true, database);
     gotExpectedError = false;
     try
     {
         database.PutRevision(revFake, null, false, status);
     }
     catch (CouchbaseLiteException e)
     {
         gotExpectedError = e.GetCBLStatus().GetCode() == Status.NotFound;
     }
     NUnit.Framework.Assert.IsTrue(gotExpectedError);
     // Read it back (should fail):
     readRev = database.GetDocumentWithIDAndRev(revD.GetDocId(), null, EnumSet.NoneOf<
         Database.TDContentOptions>());
     NUnit.Framework.Assert.IsNull(readRev);
     // Get Changes feed
     changes = database.ChangesSince(0, null, null);
     NUnit.Framework.Assert.IsTrue(changes.Count == 1);
     // Get Revision History
     IList<RevisionInternal> history = database.GetRevisionHistory(revD);
     NUnit.Framework.Assert.AreEqual(revD, history[0]);
     NUnit.Framework.Assert.AreEqual(rev2, history[1]);
     NUnit.Framework.Assert.AreEqual(rev1, history[2]);
 }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.CblID();
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            // Check for doc expiration
            var expirationTime = default(DateTime?);
            var tmp = default(object);
            var props = rev.GetProperties();
            var hasValue = false;
            if(props != null && props.TryGetValue("_exp", out tmp)) {
                hasValue = true;
                if(tmp != null) {
                    try {
                        expirationTime = Convert.ToDateTime(tmp);
                    } catch(Exception) {
                        try {
                            var num = Convert.ToInt64(tmp);
                            expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num));
                        } catch(Exception) {
                            Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp);
                            return StatusCode.BadRequest;
                        }

                    }
                }
            
                props.Remove("_exp");
                rev.SetProperties(props);
            }

            var castContext = context as ICouchbaseListenerContext2;
            var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            StatusCode status = deleting ? StatusCode.Ok : StatusCode.Created;
            try {
                if(docId != null && docId.StartsWith("_local")) {
                    if(expirationTime.HasValue) {
                        return StatusCode.BadRequest;
                    }

                    outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS
                } else {
                    outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source);
                    if(hasValue) {
                        db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime);
                    }
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }
 internal RevisionInternal(Body body)
     : this(body.GetProperties().CblID(), body.GetProperties().CblRev(), body.GetProperties().CblDeleted())
 {
     _body = body;
 }