/// <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); }
/// <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())); }