Esempio n. 1
0
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            var retVal = new AtomicAction(() =>
                                          ForestDBBridge.Check(err =>
            {
                var newc4key = default(C4EncryptionKey);
                if (newKey != null)
                {
                    newc4key = new C4EncryptionKey(newKey.KeyData);
                }

                return(Native.c4db_rekey(Forest, &newc4key, err));
            }), null, null);

            foreach (var viewName in GetAllViews())
            {
                var store = GetViewStorage(viewName, false) as ForestDBViewStore;
                if (store == null)
                {
                    continue;
                }

                retVal.AddLogic(store.ActionToChangeEncryptionKey(newKey));
            }

            return(retVal);
        }
Esempio n. 2
0
        private void WithC4Document(string docId, string revId, bool withBody, bool create, C4DocumentActionDelegate block)
        {
            var doc = default(C4Document *);

            try {
                doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, docId, !create, err));
                if (revId != null)
                {
                    ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, revId, withBody, err));
                }

                if (withBody)
                {
                    ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err));
                }
            } catch (CBForestException e) {
                var is404 = e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.KeyNotFound;
                is404 |= e.Domain == C4ErrorDomain.HTTP && e.Code == 404;
                var is410 = e.Domain == C4ErrorDomain.HTTP && e.Code == 410; // Body compacted

                if (!is404 && !is410)
                {
                    throw;
                }

                Native.c4doc_free(doc); // In case the failure was in selectRevision
                doc = null;
            }

            try {
                block(doc);
            } finally {
                Native.c4doc_free(doc);
            }
        }
Esempio n. 3
0
        public void ForceInsert(RevisionInternal inRev, IList <string> revHistory, StoreValidation validationBlock, Uri source)
        {
            if (_config.HasFlag(C4DatabaseFlags.ReadOnly))
            {
                throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden);
            }

            var json   = Manager.GetObjectMapper().WriteValueAsString(inRev.GetProperties(), true);
            var change = default(DocumentChange);

            RunInTransaction(() =>
            {
                // First get the CBForest doc:
                WithC4Document(inRev.GetDocId(), null, false, true, doc =>
                {
                    ForestDBBridge.Check(err => Native.c4doc_insertRevisionWithHistory(doc, json, inRev.IsDeleted(),
                                                                                       inRev.GetAttachments() != null, revHistory.ToArray(), err));

                    // Save updated doc back to the database:
                    var isWinner = SaveDocument(doc, revHistory[0], inRev.GetProperties());
                    inRev.SetSequence((long)doc->sequence);
                    change = ChangeWithNewRevision(inRev, isWinner, doc, source);
                });

                return(true);
            });

            if (change != null && Delegate != null)
            {
                Delegate.DatabaseStorageChanged(change);
            }
        }
Esempio n. 4
0
        private IDictionary <string, object> GetAllDocsEntry(string docId)
        {
            var value       = default(IDictionary <string, object>);
            var existingDoc = default(C4Document *);

            try {
                existingDoc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, docId, true, err));
                if (existingDoc != null)
                {
                    value = new NonNullDictionary <string, object> {
                        { "rev", (string)existingDoc->revID },
                        { "deleted", true }
                    };
                }
            } catch (CBForestException e) {
                if (e.Domain != C4ErrorDomain.ForestDB || e.Code != (int)ForestDBStatus.KeyNotFound)
                {
                    throw;
                }
            } finally {
                Native.c4doc_free(existingDoc);
            }

            return(value);
        }
Esempio n. 5
0
        public bool RunInTransaction(RunInTransactionDelegate block)
        {
            Log.D(TAG, "BEGIN transaction...");
            ForestDBBridge.Check(err => Native.c4db_beginTransaction(Forest, err));
            var success = false;

            try {
                success = block();
            } catch (CouchbaseLiteException) {
                Log.W(TAG, "Failed to run transaction");
                success = false;
                throw;
            } catch (Exception e) {
                success = false;
                throw new CouchbaseLiteException("Error running transaction", e)
                      {
                          Code = StatusCode.Exception
                      };
            } finally {
                Log.D(TAG, "END transaction (success={0})", success);
                ForestDBBridge.Check(err => Native.c4db_endTransaction(Forest, success, err));
                if (!InTransaction && Delegate != null)
                {
                    Delegate.StorageExitedTransaction(success);
                }
            }

            return(success);
        }
Esempio n. 6
0
        private C4View *OpenIndexWithOptions(C4DatabaseFlags options, bool dryRun = false)
        {
            if (_indexDB == null)
            {
                _indexDB = (C4View *)ForestDBBridge.Check(err =>
                {
                    var encryptionKey = default(C4EncryptionKey);
                    if (_dbStorage.EncryptionKey != null)
                    {
                        encryptionKey = new C4EncryptionKey(_dbStorage.EncryptionKey.KeyData);
                    }

                    return(Native.c4view_open(_dbStorage.Forest, _path, Name, dryRun  ? "0" : Delegate.MapVersion, options,
                                              &encryptionKey, err));
                });

                if (dryRun)
                {
                    ForestDBBridge.Check(err => Native.c4view_close(_indexDB, err));
                    _indexDB = null;
                }
            }

            return(_indexDB);
        }
Esempio n. 7
0
        private void CloseIndex()
        {
            var indexDB = _indexDB;

            _indexDB = null;
            if (indexDB != null)
            {
                Log.D(TAG, "Closing index");
                ForestDBBridge.Check(err => Native.c4view_close(indexDB, err));
            }
        }
Esempio n. 8
0
        public void Close()
        {
            IsOpen = false;
            var connections = _fdbConnections;

            _fdbConnections = new ConcurrentDictionary <int, IntPtr>();
            foreach (var ptr in connections)
            {
                ForestDBBridge.Check(err => Native.c4db_close((C4Database *)ptr.Value.ToPointer(), err));
            }
        }
Esempio n. 9
0
        public RevisionInternal PutLocalRevision(RevisionInternal revision, string prevRevId, bool obeyMVCC)
        {
            var docId = revision.GetDocId();

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

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

            var result = default(RevisionInternal);

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

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

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

                return(true);
            });

            return(result);
        }
Esempio n. 10
0
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            return(new AtomicAction(() =>
                                    ForestDBBridge.Check(err =>
            {
                var newc4key = default(C4EncryptionKey);
                if (newKey != null)
                {
                    newc4key = new C4EncryptionKey(newKey.KeyData);
                }

                return Native.c4view_rekey(_indexDB, &newc4key, err);
            }), null, null));
        }
Esempio n. 11
0
        private bool SaveDocument(C4Document *doc, string revId, IDictionary <string, object> properties)
        {
            // Is the new revision the winner?
            Native.c4doc_selectCurrentRevision(doc);
            bool isWinner = (string)doc->selectedRev.revID == revId;

            // Update the documentType:
            if (isWinner)
            {
                var type = properties == null ? null : properties.GetCast <string>("type");
                ForestDBBridge.Check(err => Native.c4doc_setType(doc, type, err));
            }

            // Save:
            ForestDBBridge.Check(err => Native.c4doc_save(doc, (uint)MaxRevTreeDepth, err));
            return(isWinner);
        }
Esempio n. 12
0
        public ICollection <BlobKey> FindAllAttachmentKeys()
        {
            var keys    = new HashSet <BlobKey>();
            var options = C4EnumeratorOptions.DEFAULT;

            options.flags &= ~C4EnumeratorFlags.IncludeBodies;
            options.flags |= C4EnumeratorFlags.IncludeDeleted;
            var e = new CBForestDocEnumerator(Forest, null, null, options);

            foreach (var next in e)
            {
                var docInfo = next.DocumentInfo;
                if (!docInfo->HasAttachments || (docInfo->IsDeleted && !docInfo->IsConflicted))
                {
                    continue;
                }

                var doc = next.GetDocument();
                // Since db is assumed to have just been compacted, we know that non-current revisions
                // won't have any bodies. So only scan the current revs.
                do
                {
                    if (doc->selectedRev.IsActive && doc->selectedRev.HasAttachments)
                    {
                        ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err));
                        var body = doc->selectedRev.body;
                        if (body.size > 0)
                        {
                            var rev = Manager.GetObjectMapper().ReadValue <IDictionary <string, object> >(body);
                            foreach (var entry in rev.Get("_attachments").AsDictionary <string, IDictionary <string, object> >())
                            {
                                try {
                                    var key = new BlobKey(entry.Value.GetCast <string>("digest"));
                                    keys.Add(key);
                                } catch (Exception) {
                                    Log.W(TAG, "Invalid digest {0}; skipping", entry.Value.GetCast <string>("digest"));
                                }
                            }
                        }
                    }
                } while(Native.c4doc_selectNextLeafRevision(doc, true, true, null));
            }

            return(keys);
        }
Esempio n. 13
0
        private void WithC4Document(string docId, long sequence, C4DocumentActionDelegate block)
        {
            var doc = default(C4Document *);

            try {
                doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_getBySequence(Forest, (ulong)sequence, err));
            } catch (CBForestException e) {
                if (e.Domain != C4ErrorDomain.ForestDB && (ForestDBStatus)e.Code != ForestDBStatus.KeyNotFound)
                {
                    throw;
                }
            }

            try {
                block(doc);
            } finally {
                Native.c4doc_free(doc);
            }
        }
Esempio n. 14
0
        private void WithC4Raw(string docId, string storeName, C4RawDocumentActionDelegate block)
        {
            var doc = default(C4RawDocument *);

            try {
                doc = (C4RawDocument *)ForestDBBridge.Check(err => Native.c4raw_get(Forest, storeName, docId, err));
            } catch (CBForestException e) {
                if (e.Domain != C4ErrorDomain.ForestDB && (ForestDBStatus)e.Code != ForestDBStatus.KeyNotFound)
                {
                    throw;
                }
            }

            try {
                block(doc);
            } finally {
                Native.c4raw_free(doc);
            }
        }
Esempio n. 15
0
        public RevisionInternal GetParentRevision(RevisionInternal rev)
        {
            var retVal = default(RevisionInternal);

            WithC4Document(rev.GetDocId(), rev.GetRevId(), false, false, doc =>
            {
                if (!Native.c4doc_selectParentRevision(doc))
                {
                    return;
                }

                ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err));
                retVal = new RevisionInternal((string)doc->docID, (string)doc->selectedRev.revID, doc->selectedRev.IsDeleted);
                retVal.SetSequence((long)doc->selectedRev.sequence);
                retVal.SetBody(new Body(doc->selectedRev.body));
            });

            return(retVal);
        }
Esempio n. 16
0
        private CBForestQueryEnumerator QueryEnumeratorWithOptions(QueryOptions options)
        {
            Debug.Assert(_indexDB != null);
            var enumerator = default(C4QueryEnumerator *);

            using (var startkeydocid_ = new C4String(options.StartKeyDocId))
                using (var endkeydocid_ = new C4String(options.EndKeyDocId)) {
                    WithC4Keys(new object[] { options.StartKey, options.EndKey }, false, startEndKey =>
                               WithC4Keys(options.Keys == null ? null : options.Keys.ToArray(), true, c4keys =>
                    {
                        var opts            = C4QueryOptions.DEFAULT;
                        opts.descending     = options.Descending;
                        opts.endKey         = startEndKey[1];
                        opts.endKeyDocID    = endkeydocid_.AsC4Slice();
                        opts.inclusiveEnd   = options.InclusiveEnd;
                        opts.inclusiveStart = options.InclusiveStart;
                        if (c4keys != null)
                        {
                            opts.keysCount = (uint)c4keys.Length;
                        }

                        opts.limit             = (ulong)options.Limit;
                        opts.skip              = (ulong)options.Skip;
                        opts.startKey          = startEndKey[0];
                        opts.startKeyDocID     = startkeydocid_.AsC4Slice();
                        fixed(C4Key * *keysPtr = c4keys)
                        {
                            opts.keys  = keysPtr;
                            enumerator = (C4QueryEnumerator *)ForestDBBridge.Check(err => {
                                var localOpts = opts;
                                return(Native.c4view_query(_indexDB, &localOpts, err));
                            });
                        }
                    })
                               );
                }

            return(new CBForestQueryEnumerator(enumerator));
        }
Esempio n. 17
0
        private void DeleteLocalRevision(string docId, string revId, bool obeyMVCC)
        {
            if (!docId.StartsWith("_local/"))
            {
                throw new CouchbaseLiteException("Local revision IDs must start with _local/", StatusCode.BadId);
            }

            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 new CouchbaseLiteException(StatusCode.NotFound);
                }

                throw new CouchbaseLiteException(StatusCode.Conflict);
            }

            RunInTransaction(() =>
            {
                WithC4Raw(docId, "_local", doc =>
                {
                    if (doc == null)
                    {
                        throw new CouchbaseLiteException(StatusCode.NotFound);
                    }

                    if (obeyMVCC && (revId != (string)doc->meta))
                    {
                        throw new CouchbaseLiteException(StatusCode.Conflict);
                    }

                    ForestDBBridge.Check(err => Native.c4raw_put(Forest, "_local", docId, null, null, err));
                });
                return(true);
            });
        }
Esempio n. 18
0
        private CBForestHistoryEnumerator GetHistoryEnumerator(RevisionInternal rev, int generation)
        {
            if (generation <= 1)
            {
                return(null);
            }

            var doc = default(C4Document *);

            try {
                doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, rev.GetDocId(), true, err));
                ForestDBBridge.Check(err => Native.c4doc_selectCurrentRevision(doc));
            } catch (CBForestException e) {
                if (e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.KeyNotFound)
                {
                    return(null);
                }

                throw;
            }

            return(new CBForestHistoryEnumerator(doc, false, true));
        }
Esempio n. 19
0
        private C4Database *Reopen()
        {
            var forestPath = Path.Combine(Directory, DB_FILENAME);

            try {
                return((C4Database *)ForestDBBridge.Check(err =>
                {
                    var nativeKey = default(C4EncryptionKey);
                    if (_encryptionKey != null)
                    {
                        nativeKey = new C4EncryptionKey(_encryptionKey.KeyData);
                    }

                    return Native.c4db_open(forestPath, _config, &nativeKey, err);
                }));
            } catch (CBForestException e) {
                if (e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.NoDbHeaders)
                {
                    throw new CouchbaseLiteException(StatusCode.Unauthorized);
                }

                throw;
            }
        }
Esempio n. 20
0
        public IDictionary <string, object> PurgeRevisions(IDictionary <string, IList <string> > docsToRev)
        {
            // <http://wiki.apache.org/couchdb/Purge_Documents>
            IDictionary <string, object> result = new Dictionary <string, object>();

            if (docsToRev.Count == 0)
            {
                return(result);
            }

            Log.D(TAG, "Purging {0} docs...", docsToRev.Count);
            RunInTransaction(() =>
            {
                foreach (var docRevPair in docsToRev)
                {
                    var docID = docRevPair.Key;
                    WithC4Document(docID, null, false, false, doc => {;
                                                                      if (!doc->Exists)
                                                                      {
                                                                          throw new CouchbaseLiteException(StatusCode.NotFound);
                                                                      }
                                                                      var revsPurged = default(IList <string>);
                                                                      var revIDs     = docRevPair.Value;
                                                                      if (revIDs.Count == 0)
                                                                      {
                                                                          revsPurged = new List <string>();
                                                                      }
                                                                      else if (revIDs.Contains("*"))
                                                                      {
                                                                          // Delete all revisions if magic "*" revision ID is given:
                                                                          ForestDBBridge.Check(err => Native.c4db_purgeDoc(Forest, doc->docID, err));
                                                                          revsPurged = new List <string> {
                                                                              "*"
                                                                          };
                                                                          Log.D(TAG, "Purged document '{0}'", docID);
                                                                      }
                                                                      else
                                                                      {
                                                                          var purged = new List <string>();
                                                                          foreach (var revID in revIDs)
                                                                          {
                                                                              if (Native.c4doc_purgeRevision(doc, revID, null) > 0)
                                                                              {
                                                                                  purged.Add(revID);
                                                                              }
                                                                          }

                                                                          if (purged.Count > 0)
                                                                          {
                                                                              ForestDBBridge.Check(err => Native.c4doc_save(doc, (uint)MaxRevTreeDepth, err));
                                                                              Log.D(TAG, "Purged doc '{0}' revs {1}", docID, Manager.GetObjectMapper().WriteValueAsString(revIDs));
                                                                          }

                                                                          revsPurged = purged;
                                                                      }

                                                                      result[docID] = revsPurged; });
                }

                return(true);
            });

            return(result);
        }
Esempio n. 21
0
 public void DeleteIndex()
 {
     ForestDBBridge.Check(err => Native.c4view_eraseIndex(_indexDB, err));
 }
Esempio n. 22
0
 public void Compact()
 {
     ForestDBBridge.Check(err => Native.c4db_compact(Forest, err));
 }
Esempio n. 23
0
 public void DeleteView()
 {
     _dbStorage.ForgetViewStorage(Name);
     ForestDBBridge.Check(err => Native.c4view_delete(_indexDB, err));
 }
Esempio n. 24
0
        public bool UpdateIndexes(IEnumerable <IViewStore> views)
        {
            Log.D(TAG, "Checking indexes of ({0}) for {1}", ViewNames(views), Name);

            // Creates an array of tuples -> [[view1, view1 last sequence, view1 native handle],
            // [view2, view2 last sequence, view2 native handle], ...]
            var viewsArray  = views.Cast <ForestDBViewStore>().ToArray();
            var viewInfo    = viewsArray.Select(x => Tuple.Create(x, x.LastSequenceIndexed)).ToArray();
            var nativeViews = new C4View *[viewsArray.Length];

            for (int i = 0; i < viewsArray.Length; i++)
            {
                nativeViews[i] = viewsArray[i]._indexDB;
            }

            var indexer = (C4Indexer *)ForestDBBridge.Check(err => Native.c4indexer_begin(_dbStorage.Forest, nativeViews, err));

            var enumerator = new CBForestDocEnumerator(indexer);

            var commit = false;

            try {
                foreach (var next in enumerator)
                {
                    var seq = next.SelectedRev.sequence;

                    for (int i = 0; i < viewInfo.Length; i++)
                    {
                        var info = viewInfo[i];
                        if (seq <= (ulong)info.Item2)
                        {
                            continue; // This view has already indexed this sequence
                        }

                        var viewDelegate = info.Item1.Delegate;
                        if (viewDelegate == null || viewDelegate.Map == null)
                        {
                            Log.V(TAG, "    {0} has no map block; skipping it", info.Item1.Name);
                            continue;
                        }

                        var rev    = new RevisionInternal(next, true);
                        var keys   = new List <object>();
                        var values = new List <string>();
                        try {
                            viewDelegate.Map(rev.GetProperties(), (key, value) =>
                            {
                                keys.Add(key);
                                values.Add(Manager.GetObjectMapper().WriteValueAsString(value));
                            });
                        } catch (Exception e) {
                            Log.W(TAG, String.Format("Exception thrown in map function of {0}", info.Item1.Name), e);
                            continue;
                        }

                        WithC4Keys(keys.ToArray(), true, c4keys =>
                                   ForestDBBridge.Check(err => Native.c4indexer_emit(indexer, next.Document, (uint)i, c4keys, values.ToArray(), err))
                                   );
                    }
                }

                commit = true;
            } catch (Exception e) {
                Log.W(TAG, "Error updates indexes", e);
            } finally {
                ForestDBBridge.Check(err => Native.c4indexer_end(indexer, commit, err));
            }

            return(true);
        }
Esempio n. 25
0
 public void SetInfo(string key, string info)
 {
     ForestDBBridge.Check(err => Native.c4raw_put(Forest, "info", key, null, info, err));
 }
Esempio n. 26
0
 private void LoadRevisionBody(CBForestDocStatus status)
 {
     ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(status.GetDocument(), err));
 }
Esempio n. 27
0
 private void SelectCurrentRevision(CBForestDocStatus status)
 {
     ForestDBBridge.Check(err => Native.c4doc_selectCurrentRevision(status.GetDocument()));
 }
Esempio n. 28
0
        public RevisionInternal PutRevision(string inDocId, string inPrevRevId, IDictionary <string, object> properties,
                                            bool deleting, bool allowConflict, StoreValidation validationBlock)
        {
            if (_config.HasFlag(C4DatabaseFlags.ReadOnly))
            {
                throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden);
            }

            var json = default(string);

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

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

            var putRev  = default(RevisionInternal);
            var change  = default(DocumentChange);
            var success = RunInTransaction(() =>
            {
                var docId              = inDocId;
                var prevRevId          = inPrevRevId;
                var transactionSuccess = false;
                WithC4Document(docId, null, false, true, doc =>
                {
                    if (prevRevId != null)
                    {
                        // Updating an existing revision; make sure it exists and is a leaf:
                        ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, prevRevId, false, err));
                        if (!allowConflict && !doc->selectedRev.IsLeaf)
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }
                    else
                    {
                        // No parent revision given:
                        if (deleting)
                        {
                            // Didn't specify a revision to delete: NotFound or a Conflict, depending
                            throw new CouchbaseLiteException(doc->Exists ? StatusCode.Conflict : StatusCode.NotFound);
                        }

                        // If doc exists, current rev must be in a deleted state or there will be a conflict:
                        if (Native.c4doc_selectCurrentRevision(doc))
                        {
                            if (doc->selectedRev.IsDeleted)
                            {
                                // New rev will be child of the tombstone:
                                prevRevId = (string)doc->revID;
                            }
                            else
                            {
                                throw new CouchbaseLiteException(StatusCode.Conflict);
                            }
                        }
                    }

                    // Compute the new revID. (Can't be done earlier because prevRevID may have changed.)
                    var newRevID = Delegate != null ? Delegate.GenerateRevID(Encoding.UTF8.GetBytes(json), deleting, prevRevId) : null;
                    if (newRevID == null)
                    {
                        throw new CouchbaseLiteException(StatusCode.BadId);
                    }

                    putRev = new RevisionInternal(docId, newRevID, deleting);
                    if (properties != null)
                    {
                        properties["_id"]  = docId;
                        properties["_rev"] = newRevID;
                        putRev.SetProperties(properties);
                    }

                    // Run any validation blocks:
                    if (validationBlock != null)
                    {
                        var prevRev = default(RevisionInternal);
                        if (prevRevId != null)
                        {
                            prevRev = new RevisionInternal(docId, prevRevId, doc->selectedRev.IsDeleted);
                        }

                        var status = validationBlock(putRev, prevRev, prevRevId);
                        if (status.IsError)
                        {
                            throw new CouchbaseLiteException(String.Format("{0} failed validation", putRev),
                                                             status.Code);
                        }
                    }

                    // Add the revision to the database:
                    ForestDBBridge.Check(err => Native.c4doc_insertRevision(doc, newRevID, json, deleting,
                                                                            putRev.GetAttachments() != null, allowConflict, err));
                    var isWinner = SaveDocument(doc, newRevID, properties);
                    putRev.SetSequence((long)doc->sequence);
                    change             = ChangeWithNewRevision(putRev, isWinner, doc, null);
                    transactionSuccess = true;
                });

                return(transactionSuccess);
            });

            if (!success)
            {
                return(null);
            }

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

            return(putRev);
        }