/// <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 DocumentProducerTree currentDocumentProducerTree = this.PopCurrentDocumentProducerTree(); // This might be the first time we have seen this document producer tree so we need to buffer documents if (currentDocumentProducerTree.Current == null) { await currentDocumentProducerTree.MoveNextAsync(token); } int itemsLeftInCurrentPage = currentDocumentProducerTree.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(currentDocumentProducerTree.Current); await currentDocumentProducerTree.MoveNextAsync(token); } if (currentDocumentProducerTree.HasMoreResults) { this.PushCurrentDocumentProducerTree(currentDocumentProducerTree); } // 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> /// 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. DocumentProducerTree currentDocumentProducerTree = this.PopCurrentDocumentProducerTree(); OrderByQueryResult orderByQueryResult = new OrderByQueryResult(currentDocumentProducerTree.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(currentDocumentProducerTree.CurrentDocumentProducerTree.Root)) { ++this.skipCount; } else { this.skipCount = 0; } this.previousRid = orderByQueryResult.Rid; await currentDocumentProducerTree.MoveNextAsync(cancellationToken); this.PushCurrentDocumentProducerTree(currentDocumentProducerTree); } return(new FeedResponse <CosmosElement>( results, results.Count, this.GetResponseHeaders(), false, this.GetQueryMetrics(), null, null, this.GetAndResetResponseLengthBytes())); }