/// <summary>
        /// Drains documents from this execution context.
        /// </summary>
        /// <param name="maxElements">The maximum number of documents to drains.</param>
        /// <param name="token">The cancellation token.</param>
        /// <returns>A task that when awaited on returns a FeedResponse of results.</returns>
        public override async Task <FeedResponse <CosmosElement> > DrainAsync(int maxElements, CancellationToken token)
        {
            // In order to maintain the continuation token for the user we must drain with a few constraints
            // 1) We fully drain from the left most partition before moving on to the next partition
            // 2) We drain only full pages from the document producer so we aren't left with a partial page
            //  otherwise we would need to add to the continuation token how many items to skip over on that page.

            // Only drain from the leftmost (current) document producer tree
            ItemProducerTree currentItemProducerTree = this.PopCurrentItemProducerTree();

            // This might be the first time we have seen this document producer tree so we need to buffer documents
            if (currentItemProducerTree.Current == null)
            {
                await currentItemProducerTree.MoveNextAsync(token);
            }

            int itemsLeftInCurrentPage = currentItemProducerTree.ItemsLeftInCurrentPage;

            // Only drain full pages or less if this is a top query.
            List <CosmosElement> results = new List <CosmosElement>();

            for (int i = 0; i < Math.Min(itemsLeftInCurrentPage, maxElements); i++)
            {
                results.Add(currentItemProducerTree.Current);
                await currentItemProducerTree.MoveNextAsync(token);
            }

            this.PushCurrentItemProducerTree(currentItemProducerTree);

            // At this point the document producer tree should have internally called MoveNextPage, since we fully drained a page.
            return(new FeedResponse <CosmosElement>(
                       results,
                       results.Count,
                       this.GetResponseHeaders(),
                       false,
                       this.GetQueryMetrics(),
                       null,
                       null,
                       this.GetAndResetResponseLengthBytes()));
        }
        /// <summary>
        /// A helper to move next and set the failure response if one is received
        /// </summary>
        /// <param name="itemProducerTree">The item producer tree</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>True if it move next failed. It can fail from an error or hitting the end of the tree</returns>
        protected async Task <bool> MoveNextHelperAsync(ItemProducerTree itemProducerTree, CancellationToken cancellationToken)
        {
            (bool successfullyMovedNext, QueryResponseCore? failureResponse)moveNextResponse = await itemProducerTree.MoveNextAsync(cancellationToken);

            if (moveNextResponse.failureResponse != null)
            {
                this.FailureResponse = moveNextResponse.failureResponse;
            }

            return(moveNextResponse.successfullyMovedNext);
        }
Esempio n. 3
0
        /// <summary>
        /// Drains a page of documents from this context.
        /// </summary>
        /// <param name="maxElements">The maximum number of elements.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task that when awaited on return a page of documents.</returns>
        public override async Task <FeedResponse <CosmosElement> > DrainAsync(int maxElements, CancellationToken cancellationToken)
        {
            //// In order to maintain the continuation toke for the user we must drain with a few constraints
            //// 1) We always drain from the partition, which has the highest priority item first
            //// 2) If multiple partitions have the same priority item then we drain from the left most first
            ////   otherwise we would need to keep track of how many of each item we drained from each partition
            ////   (just like parallel queries).
            //// Visually that look the following case where we have three partitions that are numbered and store letters.
            //// For teaching purposes I have made each item a tuple of the following form:
            ////      <item stored in partition, partition number>
            //// So that duplicates across partitions are distinct, but duplicates within partitions are indistinguishable.
            ////      |-------|   |-------|   |-------|
            ////      | <a,1> |   | <a,2> |   | <a,3> |
            ////      | <a,1> |   | <b,2> |   | <c,3> |
            ////      | <a,1> |   | <b,2> |   | <c,3> |
            ////      | <d,1> |   | <c,2> |   | <c,3> |
            ////      | <d,1> |   | <e,2> |   | <f,3> |
            ////      | <e,1> |   | <h,2> |   | <j,3> |
            ////      | <f,1> |   | <i,2> |   | <k,3> |
            ////      |-------|   |-------|   |-------|
            //// Now the correct drain order in this case is:
            ////  <a,1>,<a,1>,<a,1>,<a,2>,<a,3>,<b,2>,<b,2>,<c,2>,<c,3>,<c,3>,<c,3>,
            ////  <d,1>,<d,1>,<e,1>,<e,2>,<f,1>,<f,3>,<h,2>,<i,2>,<j,3>,<k,3>
            //// In more mathematical terms
            ////  1) <x, y> always comes before <z, y> where x < z
            ////  2) <i, j> always come before <i, k> where j < k

            List <CosmosElement> results = new List <CosmosElement>();

            while (!this.IsDone && results.Count < maxElements)
            {
                // Only drain from the highest priority document producer
                // We need to pop and push back the document producer tree, since the priority changes according to the sort order.
                ItemProducerTree   currentItemProducerTree = this.PopCurrentItemProducerTree();
                OrderByQueryResult orderByQueryResult      = new OrderByQueryResult(currentItemProducerTree.Current);

                // Only add the payload, since other stuff is garbage from the caller's perspective.
                results.Add(orderByQueryResult.Payload);

                // If we are at the begining of the page and seeing an rid from the previous page we should increment the skip count
                // due to the fact that JOINs can make a document appear multiple times and across continuations, so we don't want to
                // surface this more than needed. More information can be found in the continuation token docs.
                if (this.ShouldIncrementSkipCount(currentItemProducerTree.CurrentItemProducerTree.Root))
                {
                    ++this.skipCount;
                }
                else
                {
                    this.skipCount = 0;
                }

                this.previousRid = orderByQueryResult.Rid;

                await currentItemProducerTree.MoveNextAsync(cancellationToken);

                this.PushCurrentItemProducerTree(currentItemProducerTree);
            }

            return(new FeedResponse <CosmosElement>(
                       results,
                       results.Count,
                       this.GetResponseHeaders(),
                       false,
                       this.GetQueryMetrics(),
                       null,
                       null,
                       this.GetAndResetResponseLengthBytes()));
        }