public void TestForceInsertEmptyHistory() { var rev = new RevisionInternal("FakeDocId", "1-abcd".AsRevID(), false); var revProperties = new Dictionary<string, object>(); revProperties.SetDocRevID(rev.DocID, rev.RevID); revProperties["message"] = "hi"; rev.SetProperties(revProperties); IList<RevisionID> revHistory = null; database.ForceInsert(rev, revHistory, null); }
public void TestForceInsertEmptyHistory() { var rev = new RevisionInternal("FakeDocId", "1-abcd", false); var revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties["message"] = "hi"; rev.SetProperties(revProperties); IList<string> revHistory = null; database.ForceInsert(rev, revHistory, null); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestForceInsertEmptyHistory() { IList<string> revHistory = null; RevisionInternal rev = new RevisionInternal("FakeDocId", "1-tango", false, database ); IDictionary<string, object> revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties.Put("message", "hi"); rev.SetProperties(revProperties); database.ForceInsert(rev, revHistory, null); }
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); }
internal RevisionInternal CopyWithDocID(String docId, String revId) { System.Diagnostics.Debug.Assert((docId != null)); System.Diagnostics.Debug.Assert(((this._docId == null) || (this._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["_id"] = docId; properties["_rev"] = revId; result.SetProperties(properties); return(result); }
public virtual Couchbase.Lite.Internal.RevisionInternal CopyWithDocID(string docId , string revId) { System.Diagnostics.Debug.Assert(((docId != null) && (revId != null))); System.Diagnostics.Debug.Assert(((this.docId == null) || (this.docId.Equals(docId )))); Couchbase.Lite.Internal.RevisionInternal result = new Couchbase.Lite.Internal.RevisionInternal (docId, revId, deleted, database); IDictionary <string, object> unmodifiableProperties = GetProperties(); IDictionary <string, object> properties = new Dictionary <string, object>(); if (unmodifiableProperties != null) { properties.PutAll(unmodifiableProperties); } properties.Put("_id", docId); properties.Put("_rev", revId); result.SetProperties(properties); return(result); }
public RevisionInternal GetLocalDocument(string docID, string revID) { // docID already should contain "_local/" prefix RevisionInternal result = null; Cursor cursor = null; try { string[] args = new string[] { docID }; cursor = database.RawQuery("SELECT revid, json FROM localdocs WHERE docid=?", args ); if (cursor.MoveToNext()) { string gotRevID = cursor.GetString(0); if (revID != null && (!revID.Equals(gotRevID))) { return null; } byte[] json = cursor.GetBlob(1); IDictionary<string, object> properties = null; try { properties = Manager.GetObjectMapper().ReadValue<IDictionary>(json); properties.Put("_id", docID); properties.Put("_rev", gotRevID); result = new RevisionInternal(docID, gotRevID, false, this); result.SetProperties(properties); } catch (Exception e) { Log.W(Database.Tag, "Error parsing local doc JSON", e); return null; } } return result; } catch (SQLException e) { Log.E(Database.Tag, "Error getting local document", e); return null; } finally { if (cursor != null) { cursor.Close(); } } }
public void StubOutAttachmentsIn(RevisionInternal rev, int minRevPos) { if (minRevPos <= 1) { return; } IDictionary<string, object> properties = (IDictionary<string, object>)rev.GetProperties (); IDictionary<string, object> attachments = null; if (properties != null) { attachments = (IDictionary<string, object>)properties.Get("_attachments"); } IDictionary<string, object> editedProperties = null; IDictionary<string, object> editedAttachments = null; foreach (string name in attachments.Keys) { IDictionary<string, object> attachment = (IDictionary<string, object>)attachments .Get(name); int revPos = (int)attachment.Get("revpos"); object stub = attachment.Get("stub"); if (revPos > 0 && revPos < minRevPos && (stub == null)) { // Strip this attachment's body. First make its dictionary mutable: if (editedProperties == null) { editedProperties = new Dictionary<string, object>(properties); editedAttachments = new Dictionary<string, object>(attachments); editedProperties.Put("_attachments", editedAttachments); } // ...then remove the 'data' and 'follows' key: IDictionary<string, object> editedAttachment = new Dictionary<string, object>(attachment ); Sharpen.Collections.Remove(editedAttachment, "data"); Sharpen.Collections.Remove(editedAttachment, "follows"); editedAttachment.Put("stub", true); editedAttachments.Put(name, editedAttachment); Log.D(Database.Tag, "Stubbed out attachment" + rev + " " + name + ": revpos" + revPos + " " + minRevPos); } } if (editedProperties != null) { rev.SetProperties(editedProperties); } }
/// <summary> /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id. If <param name="properties"/> is null, the /// <see cref="Couchbase.Lite.Document" /> is deleted. /// </summary> /// <param name="id">The id of the local document whos contents to set.</param> /// <param name="properties">The contents to set for the local document.</param> /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs /// while setting the contents of the local document.</exception> public bool PutLocalDocument(string id, IDictionary<string, object> properties) { id = MakeLocalDocumentId(id); var rev = new RevisionInternal(id, null, properties == null); if (properties != null) { rev.SetProperties(properties); } bool ok = Storage.PutLocalRevision(rev, null, false) != null; return ok; }
private void SetupRevisionBodyTransformationFunction() { var xformer = TransformationFunction; if (xformer != null) { RevisionBodyTransformationFunction = (rev) => { var properties = rev.GetProperties(); var xformedProperties = xformer(properties); if (xformedProperties == null) { return null; } if (xformedProperties != properties) { Debug.Assert (xformedProperties != null); Debug.Assert (xformedProperties ["_id"].Equals (properties ["_id"])); Debug.Assert (xformedProperties ["_rev"].Equals (properties ["_rev"])); var nuRev = new RevisionInternal (rev.GetProperties ()); nuRev.SetProperties (xformedProperties); return nuRev; } return rev; }; } }
/// <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. /// </remarks> /// <exclude></exclude> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID) { var isSuccessful = false; if (String.IsNullOrEmpty (filename) || (body != null && contentType == null) || (oldRevID != null && docID == null) || (body != null && docID == null)) { throw new CouchbaseLiteException(StatusCode.BadRequest); } BeginTransaction(); try { var oldRev = new RevisionInternal(docID, oldRevID, false, this); if (oldRevID != null) { // Load existing revision if this is a replacement: try { LoadRevisionBody(oldRev, DocumentContentOptions.None); } catch (CouchbaseLiteException e) { if (e.GetCBLStatus().GetCode() == StatusCode.NotFound && ExistsDocumentWithIDAndRev(docID, null)) { throw new CouchbaseLiteException(StatusCode.Conflict); } } } else { // If this creates a new doc, it needs a body: oldRev.SetBody(new Body(new Dictionary<string, object>())); } // Update the _attachments dictionary: var oldRevProps = oldRev.GetProperties(); IDictionary<string, object> attachments = null; if (oldRevProps != null) { attachments = (IDictionary<string, object>)oldRevProps.Get("_attachments"); } if (attachments == null) { attachments = new Dictionary<string, object>(); } if (body != null) { var key = body.GetBlobKey(); var digest = key.Base64Digest(); var blobsByDigest = new Dictionary<string, BlobStoreWriter>(); blobsByDigest.Put(digest, body); RememberAttachmentWritersForDigests(blobsByDigest); var encodingName = (encoding == AttachmentEncoding.AttachmentEncodingGZIP) ? "gzip" : null; var dict = new Dictionary<string, object>(); dict.Put("digest", digest); dict.Put("length", body.GetLength()); dict.Put("follows", true); dict.Put("content_type", contentType); dict.Put("encoding", encodingName); attachments.Put(filename, dict); } else { if (oldRevID != null && !attachments.ContainsKey(filename)) { throw new CouchbaseLiteException(StatusCode.NotFound); } attachments.Remove(filename); } var properties = oldRev.GetProperties(); properties.Put("_attachments", attachments); oldRev.SetProperties(properties); // Create a new revision: var putStatus = new Status(); var newRev = PutRevision(oldRev, oldRevID, false, putStatus); isSuccessful = true; return newRev; } catch (SQLException e) { Log.E(Tag, "Error updating attachment", e); throw new CouchbaseLiteException(StatusCode.InternalServerError); } finally { EndTransaction(isSuccessful); } }
internal bool ProcessAttachmentsForRevision(RevisionInternal rev, string prevRevId, Status status) { if (status == null) { status = new Status(); } status.Code = StatusCode.Ok; var revAttachments = rev.GetAttachments(); if (revAttachments == null) { return true; // no-op: no attachments } // Deletions can't have attachments: if (rev.IsDeleted() || revAttachments.Count == 0) { var body = rev.GetProperties(); body.Remove("_attachments"); rev.SetProperties(body); return true; } int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1; IDictionary<string, object> parentAttachments = null; return rev.MutateAttachments((name, attachInfo) => { AttachmentInternal attachment = null; try { attachment = new AttachmentInternal(name, attachInfo); } catch(CouchbaseLiteException e) { return null; } if(attachment.EncodedContent != null) { // If there's inline attachment data, decode and store it: BlobKey blobKey = new BlobKey(); if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) { status.Code = StatusCode.AttachmentError; return null; } attachment.BlobKey = blobKey; } else if(attachInfo.GetCast<bool>("follows")) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment, attachInfo); } else if(attachInfo.GetCast<bool>("stub")) { // "stub" on an incoming revision means the attachment is the same as in the parent. if(parentAttachments == null && prevRevId != null) { parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId, status); if(parentAttachments == null) { if(status.Code == StatusCode.Ok || status.Code == StatusCode.NotFound) { status.Code = StatusCode.BadAttachment; } return null; } } var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>(); if(parentAttachment == null) { status.Code = StatusCode.BadAttachment; return null; } return parentAttachment; } // Set or validate the revpos: if(attachment.RevPos == 0) { attachment.RevPos = generation; } else if(attachment.RevPos >= generation) { status.Code = StatusCode.BadAttachment; return null; } Debug.Assert(attachment.IsValid); return attachment.AsStubDictionary(); }); }
internal bool ProcessAttachmentsForRevision(RevisionInternal rev, IList<string> ancestry) { var revAttachments = rev.GetAttachments(); if (revAttachments == null) { return true; // no-op: no attachments } // Deletions can't have attachments: if (rev.IsDeleted() || revAttachments.Count == 0) { var body = rev.GetProperties(); body.Remove("_attachments"); rev.SetProperties(body); return true; } var prevRevId = ancestry != null && ancestry.Count > 0 ? ancestry[0] : null; int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1; IDictionary<string, object> parentAttachments = null; return rev.MutateAttachments((name, attachInfo) => { AttachmentInternal attachment = null; try { attachment = new AttachmentInternal(name, attachInfo); } catch(CouchbaseLiteException) { return null; } if(attachment.EncodedContent != null) { // If there's inline attachment data, decode and store it: BlobKey blobKey = new BlobKey(); if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) { throw new CouchbaseLiteException( String.Format("Failed to write attachment ' {0}'to disk", name), StatusCode.AttachmentError); } attachment.BlobKey = blobKey; } else if(attachInfo.GetCast<bool>("follows")) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment); } else if(attachInfo.GetCast<bool>("stub")) { // "stub" on an incoming revision means the attachment is the same as in the parent. if(parentAttachments == null && prevRevId != null) { parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId); if(parentAttachments == null) { if(Attachments.HasBlobForKey(attachment.BlobKey)) { // Parent revision's body isn't known (we are probably pulling a rev along // with its entire history) but it's OK, we have the attachment already return attachInfo; } var ancestorAttachment = FindAttachment(name, attachment.RevPos, rev.GetDocId(), ancestry); if(ancestorAttachment != null) { return ancestorAttachment; } throw new CouchbaseLiteException( String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment); } } var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>(); if(parentAttachment == null) { throw new CouchbaseLiteException( String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment); } return parentAttachment; } // Set or validate the revpos: if(attachment.RevPos == 0) { attachment.RevPos = generation; } else if(attachment.RevPos > generation) { throw new CouchbaseLiteException( String.Format("Attachment specifies revision generation {0} but document is only at revision generation {1}", attachment.RevPos, generation), StatusCode.BadAttachment); } Debug.Assert(attachment.IsValid); return attachment.AsStubDictionary(); }); }
/// <summary>Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev. /// </summary> /// <remarks> /// Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev. /// Rev must already have its revID and sequence properties set. /// </remarks> internal void ExpandStoredJSONIntoRevisionWithAttachments(IEnumerable<Byte> json, RevisionInternal rev, DocumentContentOptions contentOptions) { var extra = ExtraPropertiesForRevision(rev, contentOptions); if (json != null && json.Any()) { rev.SetJson(AppendDictToJSON(json, extra)); } else { rev.SetProperties(extra); if (json == null) { rev.SetMissing(true); } } }
public void TestRevTree() { var change = default(DocumentChange); database.Changed += (sender, args) => { Assert.AreEqual(1, args.Changes.Count()); Assert.IsNull(change, "Multiple notifications posted"); change = args.Changes.First(); }; var rev = new RevisionInternal("MyDocId", "4-4444".AsRevID(), false); var revProperties = new Dictionary<string, object>(); revProperties.SetDocRevID(rev.DocID, rev.RevID); revProperties["message"] = "hi"; rev.SetProperties(revProperties); var revHistory = new List<RevisionID>(); revHistory.Add(rev.RevID); revHistory.Add("3-3333".AsRevID()); revHistory.Add("2-2222".AsRevID()); revHistory.Add("1-1111".AsRevID()); database.ForceInsert(rev, revHistory, null); Assert.AreEqual(1, database.GetDocumentCount()); VerifyRev(rev, revHistory); Assert.AreEqual(Announcement(database, rev, rev), change); Assert.IsFalse(change.IsConflict); // No-op ForceInsert of already-existing revision var lastSeq = database.GetLastSequenceNumber(); database.ForceInsert(rev, revHistory, null); Assert.AreEqual(lastSeq, database.GetLastSequenceNumber()); var conflict = new RevisionInternal("MyDocId", "5-5555".AsRevID(), false); var conflictProperties = new Dictionary<string, object>(); conflictProperties.SetDocRevID(conflict.DocID, conflict.RevID); conflictProperties["message"] = "yo"; conflict.SetProperties(conflictProperties); var conflictHistory = new List<RevisionID>(); conflictHistory.Add(conflict.RevID); conflictHistory.Add("4-4545".AsRevID()); conflictHistory.Add("3-3030".AsRevID()); conflictHistory.Add("2-2222".AsRevID()); conflictHistory.Add("1-1111".AsRevID()); change = null; database.ForceInsert(conflict, conflictHistory, null); Assert.AreEqual(1, database.GetDocumentCount()); VerifyRev(conflict, conflictHistory); Assert.AreEqual(Announcement(database, conflict, conflict), change); Assert.IsTrue(change.IsConflict); // Add an unrelated document: var other = new RevisionInternal("AnotherDocID", "1-1010".AsRevID(), false); var otherProperties = new Dictionary<string, object>(); otherProperties["language"] = "jp"; other.SetProperties(otherProperties); var otherHistory = new List<RevisionID>(); otherHistory.Add(other.RevID); change = null; database.ForceInsert(other, otherHistory, null); Assert.AreEqual(Announcement(database, other, other), change); Assert.IsFalse(change.IsConflict); // Fetch one of those phantom revisions with no body: var rev2 = database.GetDocument(rev.DocID, "2-2222".AsRevID(), true); Assert.IsTrue(rev2.Missing); Assert.IsNull(rev2.GetBody()); Assert.IsNull(database.GetDocument(rev.DocID, "666-6666".AsRevID(), true)); // Make sure no duplicate rows were inserted for the common revisions: if(_storageType == StorageEngineTypes.SQLite) { Assert.AreEqual(8, database.GetLastSequenceNumber()); } else { Assert.AreEqual(3, database.GetLastSequenceNumber()); } // Make sure the revision with the higher revID wins the conflict: var current = database.GetDocument(rev.DocID, null, true); Assert.AreEqual(conflict, current); // Check that the list of conflicts is accurate var conflictingRevs = database.Storage.GetAllDocumentRevisions(rev.DocID, true, false); CollectionAssert.AreEqual(new[] { conflict, rev }, conflictingRevs); // Get the _changes feed and verify only the winner is in it: var options = new ChangesOptions(); var changes = database.ChangesSince(0, options, null, null); CollectionAssert.AreEqual(new[] { conflict, other }, changes); options.IncludeConflicts = true; changes = database.ChangesSince(0, options, null, null); var expectedChanges = new RevisionList(); expectedChanges.Add(rev); expectedChanges.Add(conflict); expectedChanges.Add(other); var expectedChangesAlt = new RevisionList(); expectedChangesAlt.Add(conflict); expectedChangesAlt.Add(rev); expectedChangesAlt.Add(other); Assert.IsTrue(expectedChanges.SequenceEqual(changes) || expectedChangesAlt.SequenceEqual(changes)); }
internal RevisionInternal PutDocument(string docId, IDictionary<string, object> properties, string prevRevId, bool allowConflict) { bool deleting = properties == null || properties.GetCast<bool>("_deleted"); Log.D(TAG, "PUT _id={0}, _rev={1}, _deleted={2}, allowConflict={3}", docId, prevRevId, deleting, allowConflict); if ((prevRevId != null && docId == null) || (deleting && docId == null)) { throw new CouchbaseLiteException(StatusCode.BadId); } if (properties != null && properties.Get("_attachments").AsDictionary<string, object>() != null) { var tmpRev = new RevisionInternal(docId, prevRevId, deleting); tmpRev.SetProperties(properties); if (!ProcessAttachmentsForRevision(tmpRev, prevRevId == null ? null : new List<string> { prevRevId })) { return null; } properties = tmpRev.GetProperties(); } StoreValidation validationBlock = null; if (Shared.HasValues("validation", Name)) { validationBlock = ValidateRevision; } var putRev = Storage.PutRevision(docId, prevRevId, properties, deleting, allowConflict, validationBlock); if (putRev != null) { Log.D(TAG, "--> created {0}", putRev); if (!string.IsNullOrEmpty(docId)) { var dummy = default(WeakReference); UnsavedRevisionDocumentCache.TryRemove(docId, out dummy); } } return putRev; }
public bool PutLocalDocument(string id, IDictionary<string, object> properties) { // TODO: the iOS implementation wraps this in a transaction, this should do the same. id = MakeLocalDocumentId(id); RevisionInternal prevRev = GetLocalDocument(id, null); if (prevRev == null && properties == null) { return false; } bool deleted = false; if (properties == null) { deleted = true; } RevisionInternal rev = new RevisionInternal(id, null, deleted, this); if (properties != null) { rev.SetProperties(properties); } if (prevRev == null) { return PutLocalRevision(rev, null) != null; } else { return PutLocalRevision(rev, prevRev.GetRevId()) != null; } }
public void TestPulledChangesAreExternal() { var changeNotifications = 0; EventHandler<DatabaseChangeEventArgs> handler = (sender, e) => { changeNotifications++; Assert.IsTrue(e.IsExternal); }; database.Changed += handler; // Insert a dcoument as if it came from a remote source. var rev = new RevisionInternal("docId", "1-rev", false, database); var properties = new Dictionary<string, object>(); properties["_id"] = rev.GetDocId(); properties["_rev"] = rev.GetRevId(); rev.SetProperties(properties); var history = new List<string>(); history.Add(rev.GetRevId()); database.ForceInsert(rev, history, GetReplicationURL()); Assert.AreEqual(1, changeNotifications); // Analysis disable once DelegateSubtraction database.Changed -= handler; }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestRevTree() { RevisionInternal rev = new RevisionInternal("MyDocId", "4-foxy", false, database); IDictionary<string, object> revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties.Put("message", "hi"); rev.SetProperties(revProperties); IList<string> revHistory = new AList<string>(); revHistory.AddItem(rev.GetRevId()); revHistory.AddItem("3-thrice"); revHistory.AddItem("2-too"); revHistory.AddItem("1-won"); database.ForceInsert(rev, revHistory, null); NUnit.Framework.Assert.AreEqual(1, database.GetDocumentCount()); VerifyHistory(database, rev, revHistory); RevisionInternal conflict = new RevisionInternal("MyDocId", "5-epsilon", false, database ); IDictionary<string, object> conflictProperties = new Dictionary<string, object>(); conflictProperties.Put("_id", conflict.GetDocId()); conflictProperties.Put("_rev", conflict.GetRevId()); conflictProperties.Put("message", "yo"); conflict.SetProperties(conflictProperties); IList<string> conflictHistory = new AList<string>(); conflictHistory.AddItem(conflict.GetRevId()); conflictHistory.AddItem("4-delta"); conflictHistory.AddItem("3-gamma"); conflictHistory.AddItem("2-too"); conflictHistory.AddItem("1-won"); IList wasInConflict = new ArrayList(); Database.ChangeListener listener = new _ChangeListener_84(wasInConflict); database.AddChangeListener(listener); database.ForceInsert(conflict, conflictHistory, null); NUnit.Framework.Assert.IsTrue(wasInConflict.Count > 0); database.RemoveChangeListener(listener); NUnit.Framework.Assert.AreEqual(1, database.GetDocumentCount()); VerifyHistory(database, conflict, conflictHistory); // Add an unrelated document: RevisionInternal other = new RevisionInternal("AnotherDocID", "1-ichi", false, database ); IDictionary<string, object> otherProperties = new Dictionary<string, object>(); otherProperties.Put("language", "jp"); other.SetProperties(otherProperties); IList<string> otherHistory = new AList<string>(); otherHistory.AddItem(other.GetRevId()); database.ForceInsert(other, otherHistory, null); // Fetch one of those phantom revisions with no body: RevisionInternal rev2 = database.GetDocumentWithIDAndRev(rev.GetDocId(), "2-too", EnumSet.NoneOf<Database.TDContentOptions>()); NUnit.Framework.Assert.AreEqual(rev.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.AreEqual("2-too", rev2.GetRevId()); //Assert.assertNull(rev2.getContent()); // Make sure no duplicate rows were inserted for the common revisions: NUnit.Framework.Assert.AreEqual(8, database.GetLastSequenceNumber()); // Make sure the revision with the higher revID wins the conflict: RevisionInternal current = database.GetDocumentWithIDAndRev(rev.GetDocId(), null, EnumSet.NoneOf<Database.TDContentOptions>()); NUnit.Framework.Assert.AreEqual(conflict, current); // Get the _changes feed and verify only the winner is in it: ChangesOptions options = new ChangesOptions(); RevisionList changes = database.ChangesSince(0, options, null); RevisionList expectedChanges = new RevisionList(); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); NUnit.Framework.Assert.AreEqual(changes, expectedChanges); options.SetIncludeConflicts(true); changes = database.ChangesSince(0, options, null); expectedChanges = new RevisionList(); expectedChanges.AddItem(rev); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); NUnit.Framework.Assert.AreEqual(changes, expectedChanges); }
public void TestRevTree() { var rev = new RevisionInternal("MyDocId", "4-abcd", false); var revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties["message"] = "hi"; rev.SetProperties(revProperties); var revHistory = new List<string>(); revHistory.AddItem(rev.GetRevId()); revHistory.AddItem("3-abcd"); revHistory.AddItem("2-abcd"); revHistory.AddItem("1-abcd"); database.ForceInsert(rev, revHistory, null); Assert.AreEqual(1, database.DocumentCount); VerifyHistory(database, rev, revHistory); var conflict = new RevisionInternal("MyDocId", "5-abcd", false); var conflictProperties = new Dictionary<string, object>(); conflictProperties.Put("_id", conflict.GetDocId()); conflictProperties.Put("_rev", conflict.GetRevId()); conflictProperties["message"] = "yo"; conflict.SetProperties(conflictProperties); var conflictHistory = new List<string>(); conflictHistory.AddItem(conflict.GetRevId()); conflictHistory.AddItem("4-bcde"); conflictHistory.AddItem("3-bcde"); conflictHistory.AddItem("2-abcd"); conflictHistory.AddItem("1-abcd"); database.ForceInsert(conflict, conflictHistory, null); Assert.AreEqual(1, database.DocumentCount); VerifyHistory(database, conflict, conflictHistory); // Add an unrelated document: var other = new RevisionInternal("AnotherDocID", "1-cdef", false); var otherProperties = new Dictionary<string, object>(); otherProperties["language"] = "jp"; other.SetProperties(otherProperties); var otherHistory = new List<string>(); otherHistory.AddItem(other.GetRevId()); database.ForceInsert(other, otherHistory, null); // Fetch one of those phantom revisions with no body: var rev2 = database.GetDocument(rev.GetDocId(), "2-abcd", true); Assert.IsNull(rev2); // Make sure no duplicate rows were inserted for the common revisions: Assert.IsTrue(database.LastSequenceNumber <= 8); // Make sure the revision with the higher revID wins the conflict: var current = database.GetDocument(rev.GetDocId(), null, true); Assert.AreEqual(conflict, current); // Get the _changes feed and verify only the winner is in it: var options = new ChangesOptions(); var changes = database.ChangesSince(0, options, null, null); var expectedChanges = new RevisionList(); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); Assert.AreEqual(expectedChanges, changes); options.IncludeConflicts = true; changes = database.ChangesSince(0, options, null, null); expectedChanges = new RevisionList(); expectedChanges.AddItem(rev); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); var expectedChangesAlt = new RevisionList(); expectedChangesAlt.AddItem(conflict); expectedChangesAlt.AddItem(rev); expectedChangesAlt.AddItem(other); Assert.IsTrue(expectedChanges.SequenceEqual(changes) || expectedChangesAlt.SequenceEqual(changes)); }
/// <summary> /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id. If <param name="properties"/> is null, the /// <see cref="Couchbase.Lite.Document" /> is deleted. /// </summary> /// <param name="id">The id of the local document whos contents to set.</param> /// <param name="properties">The contents to set for the local document.</param> /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs /// while setting the contents of the local document.</exception> public bool PutLocalDocument(string id, IDictionary<string, object> properties) { if (!IsOpen) { Log.W(TAG, "PutLocalDocument called on closed database"); return false; } id = MakeLocalDocumentId(id); var rev = new RevisionInternal(id, null, properties == null); if (properties != null) { rev.SetProperties(properties); } bool ok = Storage.PutLocalRevision(rev, null, false) != null; return ok; }
internal RevisionInternal GetLocalDocument(string docID, string revID) { // docID already should contain "_local/" prefix RevisionInternal result = null; Cursor cursor = null; try { var args = new [] { docID }; cursor = StorageEngine.RawQuery("SELECT revid, json FROM localdocs WHERE docid=?", CommandBehavior.SequentialAccess, args); if (cursor.MoveToNext()) { var gotRevID = cursor.GetString(0); if (revID != null && (!revID.Equals(gotRevID))) { return null; } var json = cursor.GetBlob(1); IDictionary<string, object> properties = null; try { properties = Manager.GetObjectMapper().ReadValue<IDictionary<String, Object>>(json); properties["_id"] = docID; properties["_rev"] = gotRevID; result = new RevisionInternal(docID, gotRevID, false, this); result.SetProperties(properties); } catch (Exception e) { Log.W(Tag, "Error parsing local doc JSON", e); return null; } } return result; } catch (SQLException e) { Log.E(Tag, "Error getting local document", e); return null; } finally { if (cursor != null) { cursor.Close(); } } }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestValidations() { Validator validator = new _Validator_19(this); database.SetValidation("hoopy", validator); // POST a valid new document: IDictionary<string, object> props = new Dictionary<string, object>(); props.Put("name", "Zaphod Beeblebrox"); props.Put("towel", "velvet"); RevisionInternal rev = new RevisionInternal(props, database); Status status = new Status(); validationCalled = false; rev = database.PutRevision(rev, null, false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); // PUT a valid update: props.Put("head_count", 3); rev.SetProperties(props); validationCalled = false; rev = database.PutRevision(rev, rev.GetRevId(), false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); // PUT an invalid update: Sharpen.Collections.Remove(props, "towel"); rev.SetProperties(props); validationCalled = false; bool gotExpectedError = false; try { rev = database.PutRevision(rev, rev.GetRevId(), false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); // POST an invalid new document: props = new Dictionary<string, object>(); props.Put("name", "Vogon"); props.Put("poetry", true); rev = new RevisionInternal(props, database); validationCalled = false; gotExpectedError = false; try { rev = database.PutRevision(rev, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); // PUT a valid new document with an ID: props = new Dictionary<string, object>(); props.Put("_id", "ford"); props.Put("name", "Ford Prefect"); props.Put("towel", "terrycloth"); rev = new RevisionInternal(props, database); validationCalled = false; rev = database.PutRevision(rev, null, false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual("ford", rev.GetDocId()); // DELETE a document: rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database); NUnit.Framework.Assert.IsTrue(rev.IsDeleted()); validationCalled = false; rev = database.PutRevision(rev, rev.GetRevId(), false, status); NUnit.Framework.Assert.IsTrue(validationCalled); // PUT an invalid new document: props = new Dictionary<string, object>(); props.Put("_id", "petunias"); props.Put("name", "Pot of Petunias"); rev = new RevisionInternal(props, database); validationCalled = false; gotExpectedError = false; try { rev = database.PutRevision(rev, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); }
/// <summary> /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id. If <param name="properties"/> is null, the /// <see cref="Couchbase.Lite.Document" /> is deleted. /// </summary> /// <param name="id">The id of the local document whos contents to set.</param> /// <param name="properties">The contents to set for the local document.</param> /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs /// while setting the contents of the local document.</exception> public void PutLocalDocument(String id, IDictionary<String, Object> properties) { // TODO: the iOS implementation wraps this in a transaction, this should do the same. id = MakeLocalDocumentId(id); var prevRev = GetLocalDocument(id, null); if (prevRev == null && properties == null) { return; } var deleted = false || properties == null; var rev = new RevisionInternal(id, null, deleted, this); if (properties != null) { rev.SetProperties(properties); } var success = false; if (prevRev == null) { success = PutLocalRevision(rev, null) != null; } else { success = PutLocalRevision(rev, prevRev.GetRevId()) != null; } if (!success) { throw new CouchbaseLiteException("Unable to put local revision with id " + id); } }
public void TestRevTree() { var rev = new RevisionInternal("MyDocId", "4-foxy", false, database); var revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties["message"] = "hi"; rev.SetProperties(revProperties); var revHistory = new AList<string>(); revHistory.AddItem(rev.GetRevId()); revHistory.AddItem("3-thrice"); revHistory.AddItem("2-too"); revHistory.AddItem("1-won"); database.ForceInsert(rev, revHistory, null); Assert.AreEqual(1, database.DocumentCount); VerifyHistory(database, rev, revHistory); var conflict = new RevisionInternal("MyDocId", "5-epsilon", false, database); var conflictProperties = new Dictionary<string, object>(); conflictProperties.Put("_id", conflict.GetDocId()); conflictProperties.Put("_rev", conflict.GetRevId()); conflictProperties["message"] = "yo"; conflict.SetProperties(conflictProperties); var conflictHistory = new AList<string>(); conflictHistory.AddItem(conflict.GetRevId()); conflictHistory.AddItem("4-delta"); conflictHistory.AddItem("3-gamma"); conflictHistory.AddItem("2-too"); conflictHistory.AddItem("1-won"); database.ForceInsert(conflict, conflictHistory, null); Assert.AreEqual(1, database.DocumentCount); VerifyHistory(database, conflict, conflictHistory); // Add an unrelated document: var other = new RevisionInternal("AnotherDocID", "1-ichi", false, database); var otherProperties = new Dictionary<string, object>(); otherProperties["language"] = "jp"; other.SetProperties(otherProperties); var otherHistory = new AList<string>(); otherHistory.AddItem(other.GetRevId()); database.ForceInsert(other, otherHistory, null); // Fetch one of those phantom revisions with no body: var rev2 = database.GetDocumentWithIDAndRev(rev.GetDocId(), "2-too", DocumentContentOptions.None); Assert.AreEqual(rev.GetDocId(), rev2.GetDocId()); Assert.AreEqual("2-too", rev2.GetRevId()); // Make sure no duplicate rows were inserted for the common revisions: Assert.AreEqual(8, database.GetLastSequenceNumber()); // Make sure the revision with the higher revID wins the conflict: var current = database.GetDocumentWithIDAndRev(rev.GetDocId(), null, DocumentContentOptions.None); Assert.AreEqual(conflict, current); // Get the _changes feed and verify only the winner is in it: var options = new ChangesOptions(); var changes = database.ChangesSince(0, options, null); var expectedChanges = new RevisionList(); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); Assert.AreEqual(changes, expectedChanges); options.SetIncludeConflicts(true); changes = database.ChangesSince(0, options, null); expectedChanges = new RevisionList(); expectedChanges.AddItem(rev); expectedChanges.AddItem(conflict); expectedChanges.AddItem(other); Assert.AreEqual(changes, expectedChanges); }
internal RevisionInternal PutDocument(string docId, IDictionary<string, object> properties, string prevRevId, bool allowConflict, Status resultStatus) { bool deleting = properties == null || properties.GetCast<bool>("_deleted"); Log.D(TAG, "PUT _id={0}, _rev={1}, _deleted={2}, allowConflict={3}", docId, prevRevId, deleting, allowConflict); if ((prevRevId != null && docId == null) || (deleting && docId == null)) { if (resultStatus != null) { resultStatus.Code = StatusCode.BadId; return null; } } if (properties != null && properties.Get("_attachments").AsDictionary<string, object>() != null) { var tmpRev = new RevisionInternal(docId, prevRevId, deleting); tmpRev.SetProperties(properties); if (!ProcessAttachmentsForRevision(tmpRev, prevRevId, resultStatus)) { return null; } properties = tmpRev.GetProperties(); } StoreValidation validationBlock = null; if (Shared.HasValues("validation", Name)) { validationBlock = ValidateRevision; } var putRev = Storage.PutRevision(docId, prevRevId, properties, deleting, allowConflict, validationBlock, resultStatus); if (putRev != null) { Log.D(TAG, "--> created {0}", putRev); if (!string.IsNullOrEmpty(docId)) { UnsavedRevisionDocumentCache.Remove(docId); } } return putRev; }
public RevisionInternal GetLocalDocument(string docId, string revId) { if(!docId.StartsWith("_local/")) { return null; } var retVal = default(RevisionInternal); WithC4Raw(docId, "_local", doc => { if(doc == null) { return; } var gotRevId = (string)doc->meta; 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.W(TAG, "Invalid JSON for document {0}", docId); return; } properties["_id"] = docId; properties["_rev"] = gotRevId; retVal = new RevisionInternal(docId, revId, false); retVal.SetProperties(properties); }); return retVal; }
/// <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. /// </remarks> /// <exclude></exclude> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID) { if(StringEx.IsNullOrWhiteSpace(filename) || (body != null && contentType == null) || (oldRevID != null && docID == null) || (body != null && docID == null)) { throw new CouchbaseLiteException(StatusCode.BadAttachment); } 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 new CouchbaseLiteException(StatusCode.Conflict); } 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); 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 new CouchbaseLiteException(StatusCode.AttachmentNotFound); } attachments.Remove(filename); } var properties = oldRev.GetProperties(); properties["_attachments"] = attachments; oldRev.SetProperties(properties); Status status = new Status(); var newRev = PutRevision(oldRev, oldRevID, false, status); if (status.IsError) { throw new CouchbaseLiteException(status.Code); } return newRev; }
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; }
public void ExpandStoredJSONIntoRevisionWithAttachments(byte[] json, RevisionInternal rev, EnumSet<Database.TDContentOptions> contentOptions) { IDictionary<string, object> extra = ExtraPropertiesForRevision(rev, contentOptions ); if (json != null) { rev.SetJson(AppendDictToJSON(json, extra)); } else { rev.SetProperties(extra); } }
public void TestRevTreeChangeNotification() { const string DOCUMENT_ID = "MyDocId"; var rev = new RevisionInternal(DOCUMENT_ID, "1-abcd", false); var revProperties = new Dictionary<string, object>(); revProperties["_id"] = rev.GetDocId(); revProperties["_rev"] = rev.GetRevId(); revProperties["message"] = "hi"; rev.SetProperties(revProperties); var revHistory = new List<string>(); revHistory.Add(rev.GetRevId()); EventHandler<DatabaseChangeEventArgs> handler = (sender, e) => { var changes = e.Changes.ToList(); Assert.AreEqual(1, changes.Count); var change = changes[0]; Assert.AreEqual(DOCUMENT_ID, change.DocumentId); Assert.AreEqual(rev.GetRevId(), change.RevisionId); Assert.IsTrue(change.IsCurrentRevision); Assert.IsFalse(change.IsConflict); var current = database.GetDocument(change.DocumentId).CurrentRevision; Assert.AreEqual(rev.GetRevId(), current.Id); }; database.Changed += handler; database.ForceInsert(rev, revHistory, null); database.Changed -= handler; // add two more revisions to the document var rev3 = new RevisionInternal(DOCUMENT_ID, "3-abcd", false); var rev3Properties = new Dictionary<string, object>(); rev3Properties["_id"] = rev3.GetDocId(); rev3Properties["_rev"] = rev3.GetRevId(); rev3Properties["message"] = "hi again"; rev3.SetProperties(rev3Properties); var rev3History = new List<string>(); rev3History.Add(rev3.GetRevId()); rev3History.Add("2-abcd"); rev3History.Add(rev.GetRevId()); handler = (sender, e) => { var changes = e.Changes.ToList(); Assert.AreEqual(1, changes.Count); var change = changes[0]; Assert.AreEqual(DOCUMENT_ID, change.DocumentId); Assert.AreEqual(rev3.GetRevId(), change.RevisionId); Assert.IsTrue(change.IsCurrentRevision); Assert.IsFalse(change.IsConflict); var doc = database.GetDocument(change.DocumentId); Assert.AreEqual(rev3.GetRevId(), doc.CurrentRevisionId); try { Assert.AreEqual(3, doc.RevisionHistory.ToList().Count); } catch (CouchbaseLiteException) { Assert.Fail(); } }; database.Changed += handler; database.ForceInsert(rev3, rev3History, null); database.Changed -= handler; // add a conflicting revision, with the same history length as the last revision we // inserted. Since this new revision's revID has a higher ASCII sort, it should become the // new winning revision. var conflictRev = new RevisionInternal(DOCUMENT_ID, "3-bcde", false); var conflictProperties = new Dictionary<string, object>(); conflictProperties["_id"] = conflictRev.GetDocId(); conflictProperties["_rev"] = conflictRev.GetRevId(); conflictProperties["message"] = "winner"; conflictRev.SetProperties(conflictProperties); var conflictRevHistory = new List<string>(); conflictRevHistory.Add(conflictRev.GetRevId()); conflictRevHistory.Add("2-abcd"); conflictRevHistory.Add(rev.GetRevId()); handler = (sender, e) => { var changes = e.Changes.ToList(); Assert.AreEqual(1, changes.Count); var change = changes[0]; Assert.AreEqual(DOCUMENT_ID, change.DocumentId); Assert.AreEqual(conflictRev.GetRevId(), change.RevisionId); Assert.IsTrue(change.IsCurrentRevision); Assert.IsFalse(change.IsConflict); var doc = database.GetDocument(change.DocumentId); Assert.AreEqual(rev3.GetRevId(), doc.CurrentRevisionId); try { Assert.AreEqual(2, doc.ConflictingRevisions.ToList().Count); Assert.AreEqual(3, doc.RevisionHistory.ToList().Count); } catch (CouchbaseLiteException) { Assert.Fail(); } }; database.Changed += handler; database.ForceInsert(conflictRev, conflictRevHistory, null); database.Changed -= handler; }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestValidations() { Database.ValidateDelegate validator = (Revision newRevision, ValidationContext context)=> { NUnit.Framework.Assert.IsNotNull(newRevision); NUnit.Framework.Assert.IsNotNull(context); NUnit.Framework.Assert.IsTrue(newRevision.Properties != null || newRevision. IsDeletion); this._enclosing.validationCalled = true; bool hoopy = newRevision.IsDeletion || (newRevision.Properties.Get("towel" ) != null); Log.V(ValidationsTest.Tag, string.Format("--- Validating %s --> %b", newRevision. Properties, hoopy)); if (!hoopy) { context.Reject("Where's your towel?"); } return hoopy; }; database.SetValidation("hoopy", validator); // POST a valid new document: IDictionary<string, object> props = new Dictionary<string, object>(); props["name"] = "Zaphod Beeblebrox"; props["towel"] = "velvet"; RevisionInternal rev = new RevisionInternal(props, database); Status status = new Status(); validationCalled = false; rev = database.PutRevision(rev, null, false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode()); // PUT a valid update: props["head_count"] = 3; rev.SetProperties(props); validationCalled = false; rev = database.PutRevision(rev, rev.GetRevId(), false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode()); // PUT an invalid update: Sharpen.Collections.Remove(props, "towel"); rev.SetProperties(props); validationCalled = false; bool gotExpectedError = false; try { rev = database.PutRevision(rev, rev.GetRevId(), false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); // POST an invalid new document: props = new Dictionary<string, object>(); props["name"] = "Vogon"; props["poetry"] = true; rev = new RevisionInternal(props, database); validationCalled = false; gotExpectedError = false; try { rev = database.PutRevision(rev, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); // PUT a valid new document with an ID: props = new Dictionary<string, object>(); props["_id"] = "ford"; props["name"] = "Ford Prefect"; props["towel"] = "terrycloth"; rev = new RevisionInternal(props, database); validationCalled = false; rev = database.PutRevision(rev, null, false, status); NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.AreEqual("ford", rev.GetDocId()); // DELETE a document: rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database); NUnit.Framework.Assert.IsTrue(rev.IsDeleted()); validationCalled = false; rev = database.PutRevision(rev, rev.GetRevId(), false, status); NUnit.Framework.Assert.IsTrue(validationCalled); // PUT an invalid new document: props = new Dictionary<string, object>(); props["_id"] = "petunias"; props["name"] = "Pot of Petunias"; rev = new RevisionInternal(props, database); validationCalled = false; gotExpectedError = false; try { rev = database.PutRevision(rev, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden); } NUnit.Framework.Assert.IsTrue(validationCalled); NUnit.Framework.Assert.IsTrue(gotExpectedError); }
/// <summary> /// Test that the public API works as expected in change notifications after a rev tree /// insertion. /// </summary> /// <remarks> /// Test that the public API works as expected in change notifications after a rev tree /// insertion. See https://github.com/couchbase/couchbase-lite-android-core/pull/27 /// </remarks> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestRevTreeChangeNotifications() { string DocumentId = "MyDocId"; // add a document with a single (first) revision RevisionInternal rev = new RevisionInternal(DocumentId, "1-one", false, database); IDictionary<string, object> revProperties = new Dictionary<string, object>(); revProperties.Put("_id", rev.GetDocId()); revProperties.Put("_rev", rev.GetRevId()); revProperties.Put("message", "hi"); rev.SetProperties(revProperties); IList<string> revHistory = Arrays.AsList(rev.GetRevId()); Database.ChangeListener listener = new _ChangeListener_154(this, DocumentId, rev); database.AddChangeListener(listener); database.ForceInsert(rev, revHistory, null); database.RemoveChangeListener(listener); // add two more revisions to the document RevisionInternal rev3 = new RevisionInternal(DocumentId, "3-three", false, database ); IDictionary<string, object> rev3Properties = new Dictionary<string, object>(); rev3Properties.Put("_id", rev3.GetDocId()); rev3Properties.Put("_rev", rev3.GetRevId()); rev3Properties.Put("message", "hi again"); rev3.SetProperties(rev3Properties); IList<string> rev3History = Arrays.AsList(rev3.GetRevId(), "2-two", rev.GetRevId( )); listener = new _ChangeListener_182(this, DocumentId, rev3); database.AddChangeListener(listener); database.ForceInsert(rev3, rev3History, null); database.RemoveChangeListener(listener); // add a conflicting revision, with the same history length as the last revision we // inserted. Since this new revision's revID has a higher ASCII sort, it should become the // new winning revision. RevisionInternal conflictRev = new RevisionInternal(DocumentId, "3-winner", false , database); IDictionary<string, object> conflictProperties = new Dictionary<string, object>(); conflictProperties.Put("_id", conflictRev.GetDocId()); conflictProperties.Put("_rev", conflictRev.GetRevId()); conflictProperties.Put("message", "winner"); conflictRev.SetProperties(conflictProperties); IList<string> conflictRevHistory = Arrays.AsList(conflictRev.GetRevId(), "2-two", rev.GetRevId()); listener = new _ChangeListener_217(this, DocumentId, conflictRev); database.AddChangeListener(listener); database.ForceInsert(conflictRev, conflictRevHistory, null); database.RemoveChangeListener(listener); }