/// <summary> /// Runs the <see cref="Couchbase.Lite.Query"/> and returns an enumerator over the result rows. /// </summary> /// <exception cref="T:Couchbase.Lite.CouchbaseLiteException"> /// Thrown if an issue occurs while executing the <see cref="Couchbase.Lite.Query"/>. /// </exception> public virtual QueryEnumerator Run() { Log.To.Query.I(TAG, "{0} running...", this); Database.Open(); ValueTypePtr <long> outSequence = 0; var viewName = (View != null) ? View.Name : null; var queryOptions = QueryOptions; IEnumerable <QueryRow> rows = null; var success = Database.RunInTransaction(() => { rows = Database.QueryViewNamed(viewName, queryOptions, 0, outSequence); LastSequence = outSequence; return(true); }); if (!success) { throw Misc.CreateExceptionAndLog(Log.To.Query, StatusCode.DbError, TAG, "Failed to query view named {0}", viewName); } return(new QueryEnumerator(Database, rows, outSequence)); }
/// <summary> /// Runs the <see cref="Couchbase.Lite.Query"/> and returns an enumerator over the result rows. /// </summary> /// <exception cref="T:Couchbase.Lite.CouchbaseLiteException"> /// Thrown if an issue occurs while executing the <see cref="Couchbase.Lite.Query"/>. /// </exception> public virtual QueryEnumerator Run() { if (!Database.Open()) { throw new CouchbaseLiteException("The database has been closed."); } ValueTypePtr <long> outSequence = 0; var viewName = (View != null) ? View.Name : null; var queryOptions = QueryOptions; IEnumerable <QueryRow> rows = null; var success = Database.RunInTransaction(() => { rows = Database.QueryViewNamed(viewName, queryOptions, 0, outSequence); LastSequence = outSequence; return(true); }); if (!success) { throw new CouchbaseLiteException("Failed to query view named " + viewName, StatusCode.DbError); } return(new QueryEnumerator(Database, rows, outSequence)); }
public void TestWinningRevIDOfDoc() { var sqliteStorage = database.Storage as SqliteCouchStore; if (sqliteStorage == null) { Assert.Inconclusive("This test is only valid on an SQLite store"); } var properties = new Dictionary <string, object>() { { "testName", "testCreateRevisions" }, { "tag", 1337 } }; var properties2a = new Dictionary <string, object>() { { "testName", "testCreateRevisions" }, { "tag", 1338 } }; var properties2b = new Dictionary <string, object>() { { "testName", "testCreateRevisions" }, { "tag", 1339 } }; var doc = database.CreateDocument(); var newRev1 = doc.CreateRevision(); newRev1.SetUserProperties(properties); var rev1 = newRev1.Save(); ValueTypePtr <bool> outIsDeleted = false; ValueTypePtr <bool> outIsConflict = false; var docNumericId = sqliteStorage.GetDocNumericID(doc.Id); Assert.IsTrue(docNumericId != 0); Assert.AreEqual(rev1.Id, sqliteStorage.GetWinner(docNumericId, outIsDeleted, outIsConflict)); Assert.IsFalse(outIsConflict); var newRev2a = rev1.CreateRevision(); newRev2a.SetUserProperties(properties2a); var rev2a = newRev2a.Save(); Assert.AreEqual(rev2a.Id, sqliteStorage.GetWinner(docNumericId, outIsDeleted, outIsConflict)); Assert.IsFalse(outIsConflict); var newRev2b = rev1.CreateRevision(); newRev2b.SetUserProperties(properties2b); newRev2b.Save(true); sqliteStorage.GetWinner(docNumericId, outIsDeleted, outIsConflict); Assert.IsTrue(outIsConflict); }
public void TestFindMissingRevisions() { var revs = new RevisionList(); database.Storage.FindMissingRevisions(revs); var doc1r1 = PutDoc(new Dictionary<string, object> { ["_id"] = "11111", ["key"] = "one" }); var doc2r1 = PutDoc(new Dictionary<string, object> { ["_id"] = "22222", ["key"] = "two" }); PutDoc(new Dictionary<string, object> { ["_id"] = "33333", ["key"] = "three" }); PutDoc(new Dictionary<string, object> { ["_id"] = "44444", ["key"] = "four" }); PutDoc(new Dictionary<string, object> { ["_id"] = "55555", ["key"] = "five" }); var doc1r2 = PutDoc(new Dictionary<string, object> { ["_id"] = "11111", ["_rev"] = doc1r1.RevID.ToString(), ["key"] = "one+" }); var doc2r2 = PutDoc(new Dictionary<string, object> { ["_id"] = "22222", ["_rev"] = doc2r1.RevID.ToString(), ["key"] = "two+" }); PutDoc(new Dictionary<string, object> { ["_id"] = "11111", ["_rev"] = doc1r2.RevID.ToString(), ["_deleted"] = true }); // Now call FindMissingRevisions var revToFind1 = new RevisionInternal("11111", "3-6060".AsRevID(), false); var revToFind2 = new RevisionInternal("22222", doc2r2.RevID, false); var revToFind3 = new RevisionInternal("99999", "9-4141".AsRevID(), false); revs = new RevisionList(new List<RevisionInternal> { revToFind1, revToFind2, revToFind3 }); database.Storage.FindMissingRevisions(revs); CollectionAssert.AreEqual(new List<RevisionInternal> { revToFind1, revToFind3 }, revs); // Check the possible ancestors ValueTypePtr<bool> haveBodies = false; CollectionAssert.AreEqual(new List<RevisionID> { doc1r2.RevID, doc1r1.RevID }, database.Storage.GetPossibleAncestors(revToFind1, 0, haveBodies)); CollectionAssert.AreEqual(new List<RevisionID> { doc1r2.RevID }, database.Storage.GetPossibleAncestors(revToFind1, 1, haveBodies)); CollectionAssert.AreEqual(new List<RevisionID>(), database.Storage.GetPossibleAncestors(revToFind3, 0, haveBodies)); }
private IDictionary <string, object> CreatePostBody(IEnumerable <RevisionInternal> revs, Database database) { var maxRevTreeDepth = database.GetMaxRevTreeDepth(); Func <RevisionInternal, IDictionary <string, object> > invoke = source => { if (!database.IsOpen) { return(null); } //TODO: Deferred attachments ValueTypePtr <bool> haveBodies = false; var possibleAncestors = database.Storage.GetPossibleAncestors(source, Puller.MaxAttsSince, haveBodies); var key = new Dictionary <string, object> { ["id"] = source.DocID, ["rev"] = source.RevID.ToString() }; if (possibleAncestors != null) { var bodyKey = haveBodies ? "atts_since" : "revs_from"; key[bodyKey] = possibleAncestors; } else { if (source.Generation > maxRevTreeDepth) { key["revs_limit"] = maxRevTreeDepth; } } return(key); }; // Build up a JSON body describing what revisions we want: IEnumerable <IDictionary <string, object> > keys = null; try { keys = revs.Select(invoke).Where(x => x != null); } catch (Exception ex) { Log.To.Sync.E(Tag, "Error generating bulk request data.", ex); } var retval = new Dictionary <string, object>(); retval["docs"] = keys; Log.To.Sync.V(Tag, "Created bulk download request {0}{1}Body: {2}", _bulkGetUri, Environment.NewLine, new LogJsonString(keys)); return(retval); }
internal IEnumerable<QueryRow> QueryViewNamed(String viewName, QueryOptions options, long ifChangedSince, ValueTypePtr<long> outLastSequence, Status outStatus = null) { if (outStatus == null) { outStatus = new Status(); } IEnumerable<QueryRow> iterator = null; Status status = null; long lastIndexedSequence = 0, lastChangedSequence = 0; do { if(viewName != null) { var view = GetView(viewName); if(view == null) { outStatus.Code = StatusCode.NotFound; break; } lastIndexedSequence = view.LastSequenceIndexed; if(options.Stale == IndexUpdateMode.Before || lastIndexedSequence <= 0) { status = view.UpdateIndex(); if(status.IsError) { Log.W(TAG, "Failed to update index: {0}", status.Code); break; } lastIndexedSequence = view.LastSequenceIndexed; } else if(options.Stale == IndexUpdateMode.After && lastIndexedSequence <= LastSequenceNumber) { RunAsync(d => view.UpdateIndex()); } lastChangedSequence = view.LastSequenceChangedAt; iterator = view.QueryWithOptions(options); } else { // null view means query _all_docs iterator = GetAllDocs(options); lastIndexedSequence = lastChangedSequence = LastSequenceNumber; } if(lastChangedSequence <= ifChangedSince) { status = new Status(StatusCode.NotModified); } } while(false); // just to allow 'break' within the block outLastSequence.Value = lastIndexedSequence; if (status != null) { outStatus.Code = status.Code; } return iterator; }
/// <summary>Fetches the contents of a revision from the remote db, including its parent revision ID. /// </summary> /// <remarks> /// Fetches the contents of a revision from the remote db, including its parent revision ID. /// The contents are stored into rev.properties. /// </remarks> private void PullRemoteRevision(RevisionInternal rev) { // Construct a query. We want the revision history, and the bodies of attachments that have // been added since the latest revisions we have locally. // See: http://wiki.apache.org/couchdb/HTTP_Document_API#Getting_Attachments_With_a_Document var path = new StringBuilder($"/{Uri.EscapeUriString(rev.DocID)}?rev={Uri.EscapeUriString(rev.RevID.ToString())}&revs=true"); var attachments = true; if (attachments) { // TODO: deferred attachments path.Append("&attachments=true"); } // Include atts_since with a list of possible ancestor revisions of rev. If getting attachments, // this allows the server to skip the bodies of attachments that have not changed since the // local ancestor. The server can also trim the revision history it returns, to not extend past // the local ancestor (not implemented yet in SG but will be soon.) var knownRevs = default(IList <RevisionID>); ValueTypePtr <bool> haveBodies = false; try { knownRevs = LocalDatabase.Storage.GetPossibleAncestors(rev, MaxAttsSince, haveBodies)?.ToList(); } catch (Exception e) { Log.To.Sync.W(TAG, "Error getting possible ancestors (probably database closed)", e); } if (knownRevs != null) { path.Append(haveBodies ? "&atts_since=" : "&revs_from="); path.Append(JoinQuotedEscaped(knownRevs.Select(x => x.ToString()).ToList())); } else { // If we don't have any revisions at all, at least tell the server how long a history we // can keep track of: var maxRevTreeDepth = LocalDatabase.GetMaxRevTreeDepth(); if (rev.Generation > maxRevTreeDepth) { path.AppendFormat("&revs_limit={0}", maxRevTreeDepth); } } var pathInside = path.ToString(); Log.To.SyncPerf.I(TAG, "{0} getting {1}", this, rev); Log.To.Sync.V(TAG, "{0} GET {1}", this, new SecureLogString(pathInside, LogMessageSensitivity.PotentiallyInsecure)); _remoteSession.SendAsyncMultipartDownloaderRequest(HttpMethod.Get, pathInside, null, LocalDatabase, (result, e) => { // OK, now we've got the response revision: Log.To.SyncPerf.I(TAG, "{0} got {1}", this, rev); if (e != null) { Log.To.Sync.I(TAG, String.Format("{0} error pulling remote revision", this), e); LastError = e; RevisionFailed(); SafeIncrementCompletedChangesCount(); if (IsDocumentError(e)) { // Make sure this document is skipped because it is not available // even though the server is functioning _pendingSequences.RemoveSequence(rev.Sequence); LastSequence = _pendingSequences.GetCheckpointedValue(); } } else { var properties = result.AsDictionary <string, object>(); var gotRev = new PulledRevision(properties); gotRev.Sequence = rev.Sequence; if (_downloadsToInsert != null) { if (!_downloadsToInsert.QueueObject(gotRev)) { Log.To.Sync.W(TAG, "{0} failed to queue {1} for download because it is already queued, marking completed...", this, rev); SafeIncrementCompletedChangesCount(); } } else { Log.To.Sync.E(TAG, "downloadsToInsert is null"); } } // Note that we've finished this task; then start another one if there // are still revisions waiting to be pulled: PullRemoteRevisions(); }); }
internal IEnumerable<QueryRow> QueryViewNamed(String viewName, QueryOptions options, long ifChangedSince, ValueTypePtr<long> outLastSequence) { IEnumerable<QueryRow> iterator = null; long lastIndexedSequence = 0, lastChangedSequence = 0; if(viewName != null) { var view = GetView(viewName); if(view == null) { throw new CouchbaseLiteException(StatusCode.NotFound); } lastIndexedSequence = view.LastSequenceIndexed; if(options.Stale == IndexUpdateMode.Before || lastIndexedSequence <= 0) { var status = view.UpdateIndex(); if(status.IsError) { Log.W(TAG, "Failed to update index: {0}", status.Code); throw new CouchbaseLiteException(status.Code); } lastIndexedSequence = view.LastSequenceIndexed; } else if(options.Stale == IndexUpdateMode.After && lastIndexedSequence <= LastSequenceNumber) { RunAsync(d => view.UpdateIndex()); } lastChangedSequence = view.LastSequenceChangedAt; iterator = view.QueryWithOptions(options); } else { // null view means query _all_docs iterator = GetAllDocs(options); lastIndexedSequence = lastChangedSequence = LastSequenceNumber; } outLastSequence.Value = lastIndexedSequence; return iterator; }
public IEnumerable<RevisionID> GetPossibleAncestors(RevisionInternal rev, int limit, ValueTypePtr<bool> haveBodies) { haveBodies.Value = true; var returnedCount = 0; var generation = rev.RevID.Generation; for(int current = 1; current >= 0; current--) { var enumerator = GetHistoryEnumerator(rev, generation, current == 1); if(enumerator == null) { yield break; } foreach(var next in enumerator) { var flags = next.SelectedRev.flags; var tmp = Native.c4rev_getGeneration(next.SelectedRev.revID); if(flags.HasFlag(C4RevisionFlags.RevLeaf) == (current == 1) && Native.c4rev_getGeneration(next.SelectedRev.revID) < generation) { if(haveBodies && !next.HasRevisionBody) { haveBodies.Value = false; } yield return next.SelectedRev.revID.AsRevID(); if(limit > 0 && ++returnedCount >= limit) { break; } } } if(returnedCount != 0) { yield break; } } }
internal IEnumerable<QueryRow> QueryViewNamed(string viewName, QueryOptions options, long ifChangedSince, ValueTypePtr<long> outLastSequence) { IEnumerable<QueryRow> iterator = null; long lastIndexedSequence = 0, lastChangedSequence = 0; if(viewName != null) { var view = GetView(viewName); if(view == null) { throw Misc.CreateExceptionAndLog(Log.To.Query, StatusCode.NotFound, TAG, "Unable to query view named `{0}` (not found)", viewName); } lastIndexedSequence = view.LastSequenceIndexed; if(options.Stale == IndexUpdateMode.Before || lastIndexedSequence <= 0) { var status = view.UpdateIndex_Internal(); if(status.IsError) { throw Misc.CreateExceptionAndLog(Log.To.Query, status.Code, TAG, "Failed to update index for `{0}`: {1}, ", viewName, status.Code); } lastIndexedSequence = view.LastSequenceIndexed; } else if(options.Stale == IndexUpdateMode.After && lastIndexedSequence <= GetLastSequenceNumber()) { RunAsync(d => view.UpdateIndex_Internal()); } lastChangedSequence = view.LastSequenceChangedAt; iterator = view.QueryWithOptions(options); } else { // null view means query _all_docs iterator = GetAllDocs(options); lastIndexedSequence = lastChangedSequence = GetLastSequenceNumber(); } outLastSequence.Value = lastIndexedSequence; return iterator; }