public RevisionInternal GetDocument(string docId, long sequence)
        {
            var retVal = default(RevisionInternal);
            WithC4Document(docId, sequence, doc =>
            {
                Log.To.Database.D(TAG, "Read {0} seq {1}", docId, sequence);
                retVal = new ForestRevisionInternal(doc, true);
            });

            return retVal;
        }
        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 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;
        }