/// <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));
        }
Exemple #5
0
        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;
        }
Exemple #7
0
        /// <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;
        }