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); }
internal RevisionInternal Winner(Int64 docNumericID, String oldWinningRevID, Boolean oldWinnerWasDeletion, RevisionInternal newRev) { if (oldWinningRevID == null) { return newRev; } var newRevID = newRev.GetRevId(); if (!newRev.IsDeleted()) { if (oldWinnerWasDeletion || RevisionInternal.CBLCompareRevIDs(newRevID, oldWinningRevID) > 0) { return newRev; } } else { // this is now the winning live revision if (oldWinnerWasDeletion) { if (RevisionInternal.CBLCompareRevIDs(newRevID, oldWinningRevID) > 0) { return newRev; } } else { // doc still deleted, but this beats previous deletion rev // Doc was alive. How does this deletion affect the winning rev ID? var outIsDeleted = new AList<bool>(); var outIsConflict = new AList<bool>(); var winningRevID = WinningRevIDOfDoc(docNumericID, outIsDeleted, outIsConflict); if (!winningRevID.Equals(oldWinningRevID)) { if (winningRevID.Equals(newRev.GetRevId())) { return newRev; } else { var deleted = false; var winningRev = new RevisionInternal(newRev.GetDocId(), winningRevID, deleted, this); return winningRev; } } } } return null; }
private DocumentChange ChangeWithNewRevision(RevisionInternal inRev, bool isWinningRev, C4Document *doc, Uri source) { var winningRevId = default(string); if(isWinningRev) { winningRevId = inRev.GetRevId(); } else { winningRevId = (string)doc->revID; } return new DocumentChange(inRev, winningRevId, doc->IsConflicted, source); }
/// <summary>Returns an array of TDRevs in reverse chronological order, starting with the given revision. /// </summary> /// <remarks>Returns an array of TDRevs in reverse chronological order, starting with the given revision. /// </remarks> internal IList<RevisionInternal> GetRevisionHistory(RevisionInternal rev) { string docId = rev.GetDocId(); string revId = rev.GetRevId(); Debug.Assert(((docId != null) && (revId != null))); long docNumericId = GetDocNumericID(docId); if (docNumericId < 0) { return null; } else { if (docNumericId == 0) { return new AList<RevisionInternal>(); } } Cursor cursor = null; IList<RevisionInternal> result; var args = new [] { Convert.ToString(docNumericId) }; var sql = "SELECT sequence, parent, revid, deleted, json isnull FROM revs WHERE doc_id=? ORDER BY sequence DESC"; try { cursor = StorageEngine.RawQuery(sql, args); cursor.MoveToNext(); long lastSequence = 0; result = new AList<RevisionInternal>(); while (!cursor.IsAfterLast()) { var sequence = cursor.GetLong(0); var parent = cursor.GetLong(1); bool matches = false; if (lastSequence == 0) { matches = revId.Equals(cursor.GetString(2)); } else { matches = (sequence == lastSequence); } if (matches) { revId = cursor.GetString(2); var deleted = (cursor.GetInt(3) > 0); var missing = (cursor.GetInt(4) > 0); var aRev = new RevisionInternal(docId, revId, deleted, this); aRev.SetSequence(sequence); aRev.SetMissing(missing); result.AddItem(aRev); if (parent > -1) lastSequence = parent; if (lastSequence == 0) { break; } } cursor.MoveToNext(); } } catch (SQLException e) { Log.E(Tag, "Error getting revision history", e); return null; } finally { if (cursor != null) { cursor.Close(); } } return result; }
//Doesn't handle CouchbaseLiteException internal RevisionInternal LoadRevisionBody(RevisionInternal rev) { if (rev.GetSequence() > 0) { var props = rev.GetProperties(); if (props != null && props.GetCast<string>("_rev") != null && props.GetCast<string>("_id") != null) { return rev; } } Debug.Assert(rev.GetDocId() != null && rev.GetRevId() != null); Storage.LoadRevisionBody(rev); return rev; }
internal RevisionInternal TransformRevision(RevisionInternal rev) { if (RevisionBodyTransformationFunction != null) { try { var generation = rev.GetGeneration(); var xformed = RevisionBodyTransformationFunction(rev); if (xformed == null) { return null; } if (xformed != rev) { Debug.Assert((xformed.GetDocId().Equals(rev.GetDocId()))); Debug.Assert((xformed.GetRevId().Equals(rev.GetRevId()))); Debug.Assert((xformed.GetProperties().Get("_revisions").Equals(rev.GetProperties().Get("_revisions")))); if (xformed.GetProperties().ContainsKey("_attachments")) { // Insert 'revpos' properties into any attachments added by the callback: var mx = new RevisionInternal(xformed.GetProperties()); xformed = mx; mx.MutateAttachments((name, info) => { if (info.Get("revpos") != null) { return info; } if (info.Get("data") == null) { throw new InvalidOperationException("Transformer added attachment without adding data"); } var newInfo = new Dictionary<string, object>(info); newInfo["revpos"] = generation; return newInfo; }); } } } catch (Exception e) { Log.W(TAG, String.Format("Exception transforming a revision of doc '{0}'", rev.GetDocId()), e); } } return rev; }
/// <summary> /// Creates a dictionary of metadata for one specific revision /// </summary> /// <returns>The metadata dictionary</returns> /// <param name="rev">The revision to examine</param> /// <param name="responseState">The current response state</param> public static IDictionary<string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState) { if (responseState.ChangesIncludeDocs) { var status = new Status(); var rev2 = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status); if (rev2 != null) { rev2.SetSequence(rev.GetSequence()); rev = rev2; } } return new NonNullDictionary<string, object> { { "seq", rev.GetSequence() }, { "id", rev.GetDocId() }, { "changes", new List<object> { new Dictionary<string, object> { { "rev", rev.GetRevId() } } } }, { "deleted", rev.IsDeleted() ? (object)true : null }, { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null } }; }
public long GetRevisionSequence(RevisionInternal rev) { var retVal = 0L; WithC4Document(rev.GetDocId(), rev.GetRevId(), false, false, doc => retVal = (long)doc->selectedRev.sequence); return retVal; }
public void TestLocalDocs() { //create a document var documentProperties = new Dictionary<string, object>(); documentProperties["_id"] = "_local/doc1"; documentProperties["foo"] = 1; documentProperties["bar"] = false; var body = new Body(documentProperties); var rev1 = new RevisionInternal(body); rev1 = database.Storage.PutLocalRevision(rev1, null, true); Log.V(Tag, "Created " + rev1); Assert.AreEqual("_local/doc1", rev1.GetDocId()); Assert.IsTrue(rev1.GetRevId().StartsWith("1-")); //read it back var readRev = database.Storage.GetLocalDocument(rev1.GetDocId(), null); Assert.IsNotNull(readRev); var readRevProps = readRev.GetProperties(); Assert.AreEqual(rev1.GetDocId(), readRevProps.Get("_id")); Assert.AreEqual(rev1.GetRevId(), readRevProps.Get("_rev")); AssertPropertiesAreEqual(UserProperties(readRevProps), UserProperties(body.GetProperties())); //now update it documentProperties = (Dictionary<string, object>)readRev.GetProperties(); documentProperties["status"] = "updated!"; body = new Body(documentProperties); var rev2 = new RevisionInternal(body); var rev2input = rev2; rev2 = database.Storage.PutLocalRevision(rev2, rev1.GetRevId(), true); Log.V(Tag, "Updated " + rev1); Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); Assert.IsTrue(rev2.GetRevId().StartsWith("2-")); //read it back readRev = database.Storage.GetLocalDocument(rev2.GetDocId(), null); Assert.IsNotNull(readRev); AssertPropertiesAreEqual(UserProperties(readRev.GetProperties()), UserProperties(body.GetProperties())); // Try to update the first rev, which should fail: var gotException = false; try { database.Storage.PutLocalRevision(rev2input, rev1.GetRevId(), true); } catch (CouchbaseLiteException e) { Assert.AreEqual(StatusCode.Conflict, e.CBLStatus.Code); gotException = true; } Assert.IsTrue(gotException); // Delete it: var revD = new RevisionInternal(rev2.GetDocId(), null, true); gotException = false; try { var revResult = database.Storage.PutLocalRevision(revD, null, true); Assert.IsNull(revResult); } catch (CouchbaseLiteException e) { Assert.AreEqual(StatusCode.Conflict, e.CBLStatus.Code); gotException = true; } Assert.IsTrue(gotException); revD = database.Storage.PutLocalRevision(revD, rev2.GetRevId(), true); // Delete nonexistent doc: gotException = false; var revFake = new RevisionInternal("_local/fake", null, true); try { database.Storage.PutLocalRevision(revFake, null, true); } catch (CouchbaseLiteException e) { Assert.AreEqual(StatusCode.NotFound, e.CBLStatus.Code); gotException = true; } Assert.IsTrue(gotException); // Read it back (should fail): readRev = database.Storage.GetLocalDocument(revD.GetDocId(), null); Assert.IsNull(readRev); }
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; }
public IList<RevisionInternal> GetRevisionHistory(RevisionInternal rev, ICollection<string> ancestorRevIds) { var history = new List<RevisionInternal>(); WithC4Document(rev.GetDocId(), rev.GetRevId(), false, false, doc => { var enumerator = new CBForestHistoryEnumerator(doc, false); foreach(var next in enumerator) { if(ancestorRevIds != null && ancestorRevIds.Contains((string)next.Document->selectedRev.revID)) { break; } var newRev = new RevisionInternal(next.Document, false); newRev.SetMissing(!Native.c4doc_hasRevisionBody(next.Document)); history.Add(newRev); } }); return history; }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestLocalDocs() { //create a document IDictionary<string, object> documentProperties = new Dictionary<string, object>(); documentProperties.Put("_id", "_local/doc1"); documentProperties.Put("foo", 1); documentProperties.Put("bar", false); Body body = new Body(documentProperties); RevisionInternal rev1 = new RevisionInternal(body, database); Status status = new Status(); rev1 = database.PutLocalRevision(rev1, null); Log.V(Tag, "Created " + rev1); NUnit.Framework.Assert.AreEqual("_local/doc1", rev1.GetDocId()); NUnit.Framework.Assert.IsTrue(rev1.GetRevId().StartsWith("1-")); //read it back RevisionInternal readRev = database.GetLocalDocument(rev1.GetDocId(), null); NUnit.Framework.Assert.IsNotNull(readRev); IDictionary<string, object> readRevProps = readRev.GetProperties(); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), readRev.GetProperties().Get("_id" )); NUnit.Framework.Assert.AreEqual(rev1.GetRevId(), readRev.GetProperties().Get("_rev" )); NUnit.Framework.Assert.AreEqual(UserProperties(readRevProps), UserProperties(body .GetProperties())); //now update it documentProperties = readRev.GetProperties(); documentProperties.Put("status", "updated!"); body = new Body(documentProperties); RevisionInternal rev2 = new RevisionInternal(body, database); RevisionInternal rev2input = rev2; rev2 = database.PutLocalRevision(rev2, rev1.GetRevId()); Log.V(Tag, "Updated " + rev1); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.IsTrue(rev2.GetRevId().StartsWith("2-")); //read it back readRev = database.GetLocalDocument(rev2.GetDocId(), null); NUnit.Framework.Assert.IsNotNull(readRev); NUnit.Framework.Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties (body.GetProperties())); // Try to update the first rev, which should fail: bool gotException = false; try { database.PutLocalRevision(rev2input, rev1.GetRevId()); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); // Delete it: RevisionInternal revD = new RevisionInternal(rev2.GetDocId(), null, true, database ); gotException = false; try { RevisionInternal revResult = database.PutLocalRevision(revD, null); NUnit.Framework.Assert.IsNull(revResult); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); revD = database.PutLocalRevision(revD, rev2.GetRevId()); // Delete nonexistent doc: gotException = false; RevisionInternal revFake = new RevisionInternal("_local/fake", null, true, database ); try { database.PutLocalRevision(revFake, null); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.NotFound, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); // Read it back (should fail): readRev = database.GetLocalDocument(revD.GetDocId(), null); NUnit.Framework.Assert.IsNull(readRev); }
public string FindCommonAncestor(RevisionInternal rev, IEnumerable<string> revIds) { var generation = RevisionInternal.GenerationFromRevID(rev.GetRevId()); var revIdArray = revIds == null ? null : revIds.ToList(); if (generation <= 1 || revIdArray == null || revIdArray.Count == 0) { return null; } revIdArray.Sort(RevisionInternal.CBLCompareRevIDs); var commonAncestor = default(string); WithC4Document(rev.GetDocId(), null, false, false, doc => { foreach(var possibleRevId in revIds) { if(RevisionInternal.GenerationFromRevID(possibleRevId) <= generation && Native.c4doc_selectRevision(doc, possibleRevId, false, null)) { commonAncestor = possibleRevId; return; } } }); return commonAncestor; }
public IEnumerable<string> GetPossibleAncestors(RevisionInternal rev, int limit, bool onlyAttachments) { var returnedCount = 0; var generation = RevisionInternal.GenerationFromRevID(rev.GetRevId()); var enumerator = GetHistoryEnumerator(rev, generation); if(enumerator == null) { yield break; } foreach (var next in enumerator) { if(returnedCount >= limit) { break; } var revId = next.CurrentRevID; if(RevisionInternal.GenerationFromRevID(revId) < generation && !next.SelectedRev.IsDeleted && next.HasRevisionBody && !(onlyAttachments && !next.SelectedRev.HasAttachments)) { returnedCount++; yield return revId; } } }
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; }
internal Int64 InsertRevision(RevisionInternal rev, long docNumericID, long parentSequence, bool current, bool hasAttachments, IEnumerable<byte> data) { var rowId = 0L; try { var args = new ContentValues(); args["doc_id"] = docNumericID; args.Put("revid", rev.GetRevId()); if (parentSequence != 0) { args["parent"] = parentSequence; } args["current"] = current; args["deleted"] = rev.IsDeleted(); args["no_attachments"] = !hasAttachments; if (data != null) { args["json"] = data.ToArray(); } rowId = StorageEngine.Insert("revs", null, args); rev.SetSequence(rowId); } catch (Exception e) { Log.E(Tag, "Error inserting revision", e); } return rowId; }
public void TestPusher() { if (!Boolean.Parse((string)Runtime.Properties["replicationTestsEnabled"])) { Assert.Inconclusive("Replication tests disabled."); return; } using (var remoteDb = _sg.CreateDatabase(TempDbName())) { var remote = remoteDb.RemoteUri; var docIdTimestamp = Convert.ToString(Runtime.CurrentTimeMillis()); // Create some documents: var documentProperties = new Dictionary<string, object>(); var doc1Id = string.Format("doc1-{0}", docIdTimestamp); documentProperties["_id"] = doc1Id; documentProperties["foo"] = 1; documentProperties["bar"] = false; var body = new Body(documentProperties); var rev1 = new RevisionInternal(body); rev1 = database.PutRevision(rev1, null, false); documentProperties.Put("_rev", rev1.GetRevId()); documentProperties["UPDATED"] = true; database.PutRevision(new RevisionInternal(documentProperties), rev1.GetRevId(), false); documentProperties = new Dictionary<string, object>(); var doc2Id = string.Format("doc2-{0}", docIdTimestamp); documentProperties["_id"] = doc2Id; documentProperties["baz"] = 666; documentProperties["fnord"] = true; database.PutRevision(new RevisionInternal(documentProperties), null, false); var doc2 = database.GetDocument(doc2Id); var doc2UnsavedRev = doc2.CreateRevision(); var attachmentStream = GetAsset("attachment.png"); doc2UnsavedRev.SetAttachment("attachment_testPusher.png", "image/png", attachmentStream); var doc2Rev = doc2UnsavedRev.Save(); doc2UnsavedRev.Dispose(); attachmentStream.Dispose(); Assert.IsNotNull(doc2Rev); const bool continuous = false; var repl = database.CreatePushReplication(remote); repl.Continuous = continuous; if (!IsSyncGateway(remote)) { repl.CreateTarget = true; } // Check the replication's properties: Assert.AreEqual(database, repl.LocalDatabase); Assert.AreEqual(remote, repl.RemoteUrl); Assert.IsFalse(repl.IsPull); Assert.IsFalse(repl.Continuous); Assert.IsNull(repl.Filter); Assert.IsNull(repl.FilterParams); Assert.IsNull(repl.DocIds); // TODO: CAssertNil(r1.headers); still not null! // Check that the replication hasn't started running: Assert.IsFalse(repl.IsRunning); Assert.AreEqual(ReplicationStatus.Stopped, repl.Status); Assert.AreEqual(0, repl.CompletedChangesCount); Assert.AreEqual(0, repl.ChangesCount); Assert.IsNull(repl.LastError); RunReplication(repl); // TODO: Verify the foloowing 2 asserts. ChangesCount and CompletedChangesCount // should already be reset when the replicator stopped. Assert.IsNull(repl.LastError); Assert.IsTrue(repl.ChangesCount >= 2); Assert.IsTrue(repl.CompletedChangesCount >= 2); remoteDb.VerifyDocumentExists(doc1Id); // Add doc3 documentProperties = new Dictionary<string, object>(); var doc3Id = string.Format("doc3-{0}", docIdTimestamp); var doc3 = database.GetDocument(doc3Id); documentProperties["bat"] = 677; doc3.PutProperties(documentProperties); // re-run push replication var repl2 = database.CreatePushReplication(remote); repl2.Continuous = continuous; if (!IsSyncGateway(remote)) { repl2.CreateTarget = true; } var repl2CheckedpointId = repl2.RemoteCheckpointDocID(); RunReplication(repl2); Assert.IsNull(repl2.LastError); Sleep(1000); // make sure trhe doc has been added remoteDb.VerifyDocumentExists(doc3Id); Assert.AreEqual(repl2.LastSequence, database.LastSequenceWithCheckpointId(repl2CheckedpointId)); Sleep(2000); var json = GetRemoteDoc(remote, repl2CheckedpointId); var remoteLastSequence = (string)json["lastSequence"]; Assert.AreEqual(repl2.LastSequence, remoteLastSequence); } }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal LoadRevisionBody(RevisionInternal rev, DocumentContentOptions contentOptions) { if (rev.GetBody() != null && contentOptions == DocumentContentOptions.None && rev.GetSequence() != 0) { return rev; } if ((rev.GetDocId() == null) || (rev.GetRevId() == null)) { Log.E(Database.Tag, "Error loading revision body"); throw new CouchbaseLiteException(StatusCode.PreconditionFailed); } Cursor cursor = null; var result = new Status(StatusCode.NotFound); try { // TODO: on ios this query is: // TODO: "SELECT sequence, json FROM revs WHERE doc_id=@ AND revid=@ LIMIT 1" var sql = "SELECT sequence, json FROM revs, docs WHERE revid=? AND docs.docid=? AND revs.doc_id=docs.doc_id LIMIT 1"; var args = new [] { rev.GetRevId(), rev.GetDocId() }; cursor = StorageEngine.RawQuery(sql, CommandBehavior.SequentialAccess, args); if (cursor.MoveToNext()) { result.SetCode(StatusCode.Ok); rev.SetSequence(cursor.GetLong(0)); ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(1), rev, contentOptions); } } catch (SQLException e) { Log.E(Tag, "Error loading revision body", e); throw new CouchbaseLiteException(StatusCode.InternalServerError); } finally { if (cursor != null) { cursor.Close(); } } if (result.GetCode() == StatusCode.NotFound) { throw new CouchbaseLiteException(result.GetCode()); } return rev; }
internal void NotifyChange(RevisionInternal rev, RevisionInternal winningRev, Uri source, bool inConflict) { var change = new DocumentChange(rev, winningRev.GetRevId(), inConflict, source); _changesToNotify.Add(change); if (!PostChangeNotifications()) { // The notification wasn't posted yet, probably because a transaction is open. // But the Document, if any, needs to know right away so it can update its // currentRevision. var doc = DocumentCache.Get(change.DocumentId); if (doc != null) { doc.RevisionAdded(change, false); } } }
public static ICouchbaseResponseState RevsDiff(ICouchbaseListenerContext context) { // Collect all of the input doc/revision IDs as CBL_Revisions: var revs = new RevisionList(); var body = context.BodyAs<Dictionary<string, object>>(); if (body == null) { return context.CreateResponse(StatusCode.BadJson).AsDefaultState(); } foreach (var docPair in body) { var revIDs = docPair.Value.AsList<string>(); if (revIDs == null) { return context.CreateResponse(StatusCode.BadParam).AsDefaultState(); } foreach (var revID in revIDs) { var rev = new RevisionInternal(docPair.Key, revID, false); revs.Add(rev); } } return PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); // Look them up, removing the existing ones from revs: db.Storage.FindMissingRevisions(revs); // Return the missing revs in a somewhat different format: IDictionary<string, object> diffs = new Dictionary<string, object>(); foreach(var rev in revs) { var docId = rev.GetDocId(); IList<string> missingRevs = null; if(!diffs.ContainsKey(docId)) { missingRevs = new List<string>(); diffs[docId] = new Dictionary<string, IList<string>> { { "missing", missingRevs } }; } else { missingRevs = ((Dictionary<string, IList<string>>)diffs[docId])["missing"]; } missingRevs.Add(rev.GetRevId()); } // Add the possible ancestors for each missing revision: foreach(var docPair in diffs) { IDictionary<string, IList<string>> docInfo = (IDictionary<string, IList<string>>)docPair.Value; int maxGen = 0; string maxRevID = null; foreach(var revId in docInfo["missing"]) { var parsed = RevisionInternal.ParseRevId(revId); if(parsed.Item1 > maxGen) { maxGen = parsed.Item1; maxRevID = revId; } } var rev = new RevisionInternal(docPair.Key, maxRevID, false); var ancestors = db.Storage.GetPossibleAncestors(rev, 0, false); var ancestorList = ancestors == null ? null : ancestors.ToList(); if(ancestorList != null && ancestorList.Count > 0) { docInfo["possible_ancestors"] = ancestorList; } } response.JsonBody = new Body(diffs); return response; }).AsDefaultState(); }
public void LoadRevisionBody(RevisionInternal rev) { WithC4Document(rev.GetDocId(), rev.GetRevId(), true, false, doc => { if(doc == null) { throw new CouchbaseLiteException(StatusCode.NotFound); } rev.SetBody(new Body(doc->selectedRev.body)); }); }
public void TestPusherDeletedDoc() { if (!Boolean.Parse((string)Runtime.Properties["replicationTestsEnabled"])) { Assert.Inconclusive("Replication tests disabled."); return; } using (var remoteDb = _sg.CreateDatabase(TempDbName())) { var remote = remoteDb.RemoteUri; var docIdTimestamp = Convert.ToString(Runtime.CurrentTimeMillis()); // Create some documentsConvert var documentProperties = new Dictionary<string, object>(); var doc1Id = string.Format("doc1-{0}", docIdTimestamp); documentProperties["_id"] = doc1Id; documentProperties["foo"] = 1; documentProperties["bar"] = false; var body = new Body(documentProperties); var rev1 = new RevisionInternal(body); rev1 = database.PutRevision(rev1, null, false); documentProperties["_rev"] = rev1.GetRevId(); documentProperties["UPDATED"] = true; documentProperties["_deleted"] = true; database.PutRevision(new RevisionInternal(documentProperties), rev1.GetRevId(), false); var repl = database.CreatePushReplication(remote); if (!IsSyncGateway(remote)) { ((Pusher)repl).CreateTarget = true; } RunReplication(repl); Assert.IsNull(repl.LastError); // make sure doc1 is deleted var replicationUrlTrailing = new Uri(string.Format("{0}/", remote)); var pathToDoc = new Uri(replicationUrlTrailing, doc1Id); Log.D(Tag, "Send http request to " + pathToDoc); var httpRequestDoneSignal = new CountDownLatch(1); using (var httpclient = new HttpClient()) { try { var getDocResponse = httpclient.GetAsync(pathToDoc.ToString()).Result; var statusLine = getDocResponse.StatusCode; Log.D(ReplicationTest.Tag, "statusLine " + statusLine); Assert.AreEqual(Couchbase.Lite.StatusCode.NotFound, statusLine.GetStatusCode()); } catch (ProtocolViolationException e) { Assert.IsNull(e, "Got ClientProtocolException: " + e.Message); } catch (IOException e) { Assert.IsNull(e, "Got IOException: " + e.Message); } finally { httpRequestDoneSignal.CountDown(); } Log.D(Tag, "Waiting for http request to finish"); try { httpRequestDoneSignal.Await(TimeSpan.FromSeconds(10)); Log.D(Tag, "http request finished"); } catch (Exception e) { Runtime.PrintStackTrace(e); } } } }
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)); }
internal RevisionInternal RevisionByLoadingBody(RevisionInternal rev, Status outStatus) { // First check for no-op -- if we just need the default properties and already have them: if (rev.GetSequence() != 0) { var props = rev.GetProperties(); if (props != null && props.ContainsKey("_rev") && props.ContainsKey("_id")) { if (outStatus != null) { outStatus.Code = StatusCode.Ok; } return rev; } } RevisionInternal nuRev = rev.CopyWithDocID(rev.GetDocId(), rev.GetRevId()); try { LoadRevisionBody(nuRev); } catch(CouchbaseLiteException e) { if (outStatus != null) { outStatus.Code = e.CBLStatus.Code; } nuRev = null; } return nuRev; }
/// <summary>Inserts an already-existing revision replicated from a remote sqliteDb.</summary> /// <remarks> /// Inserts an already-existing revision replicated from a remote sqliteDb. /// It must already have a revision ID. This may create a conflict! The revision's history must be given; ancestor revision IDs that don't already exist locally will create phantom revisions with no content. /// </remarks> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal void ForceInsert(RevisionInternal rev, IList<string> revHistory, Uri source) { var inConflict = false; var docId = rev.GetDocId(); var revId = rev.GetRevId(); if (!IsValidDocumentId(docId) || (revId == null)) { throw new CouchbaseLiteException(StatusCode.BadRequest); } int historyCount = 0; if (revHistory != null) { historyCount = revHistory.Count; } if (historyCount == 0) { revHistory = new AList<string>(); revHistory.AddItem(revId); historyCount = 1; } else { if (!revHistory[0].Equals(rev.GetRevId())) { throw new CouchbaseLiteException(StatusCode.BadRequest); } } bool success = false; BeginTransaction(); try { // First look up all locally-known revisions of this document: long docNumericID = GetOrInsertDocNumericID(docId); RevisionList localRevs = GetAllRevisionsOfDocumentID(docId, docNumericID, false); if (localRevs == null) { throw new CouchbaseLiteException(StatusCode.InternalServerError); } IList<bool> outIsDeleted = new AList<bool>(); IList<bool> outIsConflict = new AList<bool>(); bool oldWinnerWasDeletion = false; string oldWinningRevID = WinningRevIDOfDoc(docNumericID, outIsDeleted, outIsConflict ); if (outIsDeleted.Count > 0) { oldWinnerWasDeletion = true; } if (outIsConflict.Count > 0) { inConflict = true; } // Walk through the remote history in chronological order, matching each revision ID to // a local revision. When the list diverges, start creating blank local revisions to fill // in the local history: long sequence = 0; long localParentSequence = 0; for (int i = revHistory.Count - 1; i >= 0; --i) { revId = revHistory[i]; RevisionInternal localRev = localRevs.RevWithDocIdAndRevId(docId, revId); if (localRev != null) { // This revision is known locally. Remember its sequence as the parent of the next one: sequence = localRev.GetSequence(); Debug.Assert((sequence > 0)); localParentSequence = sequence; } else { // This revision isn't known, so add it: RevisionInternal newRev; IEnumerable<Byte> data = null; bool current = false; if (i == 0) { // Hey, this is the leaf revision we're inserting: newRev = rev; if (!rev.IsDeleted()) { data = EncodeDocumentJSON(rev); if (data == null) { throw new CouchbaseLiteException(StatusCode.BadRequest); } } current = true; } else { // It's an intermediate parent, so insert a stub: newRev = new RevisionInternal(docId, revId, false, this); } // Insert it: sequence = InsertRevision(newRev, docNumericID, sequence, current, (GetAttachmentsFromRevision(newRev).Count > 0), data); if (sequence <= 0) { throw new CouchbaseLiteException(StatusCode.InternalServerError); } if (i == 0) { // Write any changed attachments for the new revision. As the parent sequence use // the latest local revision (this is to copy attachments from): var attachments = GetAttachmentsFromRevision(rev); if (attachments != null) { ProcessAttachmentsForRevision(attachments, rev, localParentSequence); StubOutAttachmentsInRevision(attachments, rev); } } } } // Mark the latest local rev as no longer current: if (localParentSequence > 0 && localParentSequence != sequence) { ContentValues args = new ContentValues(); args["current"] = 0; string[] whereArgs = new string[] { Convert.ToString(localParentSequence) }; try { var numRowsChanged = StorageEngine.Update("revs", args, "sequence=?", whereArgs); if (numRowsChanged == 0) { inConflict = true; } } catch (Exception) { throw new CouchbaseLiteException(StatusCode.InternalServerError); } } var winningRev = Winner(docNumericID, oldWinningRevID, oldWinnerWasDeletion, rev); success = true; NotifyChange(rev, winningRev, source, inConflict); } catch (SQLException) { throw new CouchbaseLiteException(StatusCode.InternalServerError); } finally { EndTransaction(success); } }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException">When attempting to add an invalid revision</exception> internal void ForceInsert(RevisionInternal inRev, IList<string> revHistory, Uri source) { if (revHistory == null) { revHistory = new List<string>(0); } var rev = inRev.CopyWithDocID(inRev.GetDocId(), inRev.GetRevId()); rev.SetSequence(0); string revID = rev.GetRevId(); if (!IsValidDocumentId(rev.GetDocId()) || revID == null) { throw new CouchbaseLiteException(StatusCode.BadId); } if (revHistory.Count == 0) { revHistory.Add(revID); } else if (revID != revHistory[0]) { throw new CouchbaseLiteException(StatusCode.BadId); } if (inRev.GetAttachments() != null) { var updatedRev = inRev.CopyWithDocID(inRev.GetDocId(), inRev.GetRevId()); string prevRevID = revHistory.Count >= 2 ? revHistory[1] : null; Status status = new Status(); if (!ProcessAttachmentsForRevision(updatedRev, prevRevID, status)) { throw new CouchbaseLiteException(status.Code); } inRev = updatedRev; } StoreValidation validationBlock = null; if (Shared != null && Shared.HasValues("validation", Name)) { validationBlock = ValidateRevision; } var insertStatus = Storage.ForceInsert(inRev, revHistory, validationBlock, source); if(insertStatus.IsError) { throw new CouchbaseLiteException(insertStatus.Code); } }
internal RevisionInternal GetParentRevision(RevisionInternal rev) { // First get the parent's sequence: var seq = rev.GetSequence(); if (seq > 0) { seq = LongForQuery("SELECT parent FROM revs WHERE sequence=?", new [] { Convert.ToString(seq) }); } else { var docNumericID = GetDocNumericID(rev.GetDocId()); if (docNumericID <= 0) { return null; } var args = new [] { Convert.ToString(docNumericID), rev.GetRevId() }; seq = LongForQuery("SELECT parent FROM revs WHERE doc_id=? and revid=?", args); } if (seq == 0) { return null; } // Now get its revID and deletion status: RevisionInternal result = null; var queryArgs = new [] { Convert.ToString(seq) }; var queryString = "SELECT revid, deleted FROM revs WHERE sequence=?"; Cursor cursor = null; try { cursor = StorageEngine.RawQuery(queryString, queryArgs); if (cursor.MoveToNext()) { string revId = cursor.GetString(0); bool deleted = (cursor.GetInt(1) > 0); result = new RevisionInternal(rev.GetDocId(), revId, deleted, this); result.SetSequence(seq); } } finally { cursor.Close(); } return result; }
/// <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>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 IDictionary<String, Object> ExtraPropertiesForRevision(RevisionInternal rev, DocumentContentOptions contentOptions) { var docId = rev.GetDocId(); var revId = rev.GetRevId(); var sequenceNumber = rev.GetSequence(); Debug.Assert((revId != null)); Debug.Assert((sequenceNumber > 0)); // Get attachment metadata, and optionally the contents: IDictionary<string, object> attachmentsDict = null; if (!contentOptions.HasFlag(DocumentContentOptions.NoAttachments)) { attachmentsDict = GetAttachmentsDictForSequenceWithContent (sequenceNumber, contentOptions); } // Get more optional stuff to put in the properties: //OPT: This probably ends up making redundant SQL queries if multiple options are enabled. var localSeq = -1L; if (contentOptions.HasFlag(DocumentContentOptions.IncludeLocalSeq)) { localSeq = sequenceNumber; } IDictionary<string, object> revHistory = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeRevs)) { revHistory = GetRevisionHistoryDict(rev); } IList<object> revsInfo = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeRevsInfo)) { revsInfo = new AList<object>(); var revHistoryFull = GetRevisionHistory(rev); foreach (RevisionInternal historicalRev in revHistoryFull) { var revHistoryItem = new Dictionary<string, object>(); var status = "available"; if (historicalRev.IsDeleted()) { status = "deleted"; } if (historicalRev.IsMissing()) { status = "missing"; } revHistoryItem.Put("rev", historicalRev.GetRevId()); revHistoryItem["status"] = status; revsInfo.AddItem(revHistoryItem); } } IList<string> conflicts = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeConflicts)) { var revs = GetAllRevisionsOfDocumentID(docId, true); if (revs.Count > 1) { conflicts = new AList<string>(); foreach (RevisionInternal savedRev in revs) { if (!(savedRev.Equals(rev) || savedRev.IsDeleted())) { conflicts.AddItem(savedRev.GetRevId()); } } } } var result = new Dictionary<string, object>(); result["_id"] = docId; result["_rev"] = revId; if (rev.IsDeleted()) { result["_deleted"] = true; } if (attachmentsDict != null) { result["_attachments"] = attachmentsDict; } if (localSeq > -1) { result["_local_seq"] = localSeq; } if (revHistory != null) { result["_revisions"] = revHistory; } if (revsInfo != null) { result["_revs_info"] = revsInfo; } if (conflicts != null) { result["_conflicts"] = conflicts; } return result; }