public async ValueTask <bool> MoveNextAsync(ITrace trace) { this.cancellationToken.ThrowIfCancellationRequested(); if (trace == null) { throw new ArgumentNullException(nameof(trace)); } if (!await this.crossPartitionRangePageAsyncEnumerator.MoveNextAsync(trace)) { this.Current = default; return(false); } TryCatch <CrossFeedRangePage <QueryPage, QueryState> > currentCrossPartitionPage = this.crossPartitionRangePageAsyncEnumerator.Current; if (currentCrossPartitionPage.Failed) { this.Current = TryCatch <QueryPage> .FromException(currentCrossPartitionPage.Exception); return(true); } CrossFeedRangePage <QueryPage, QueryState> crossPartitionPageResult = currentCrossPartitionPage.Result; QueryPage backendQueryPage = crossPartitionPageResult.Page; CrossFeedRangeState <QueryState> crossPartitionState = crossPartitionPageResult.State; QueryState queryState; if (crossPartitionState == null) { queryState = null; } else { // left most and any non null continuations IOrderedEnumerable <FeedRangeState <QueryState> > feedRangeStates = crossPartitionState .Value .ToArray() .OrderBy(tuple => ((FeedRangeEpk)tuple.FeedRange).Range.Min); List <ParallelContinuationToken> activeParallelContinuationTokens = new List <ParallelContinuationToken>(); { FeedRangeState <QueryState> firstState = feedRangeStates.First(); ParallelContinuationToken firstParallelContinuationToken = new ParallelContinuationToken( token: firstState.State != null ? ((CosmosString)firstState.State.Value).Value : null, range: ((FeedRangeEpk)firstState.FeedRange).Range); activeParallelContinuationTokens.Add(firstParallelContinuationToken); } foreach (FeedRangeState <QueryState> feedRangeState in feedRangeStates.Skip(1)) { this.cancellationToken.ThrowIfCancellationRequested(); if (feedRangeState.State != null) { ParallelContinuationToken parallelContinuationToken = new ParallelContinuationToken( token: feedRangeState.State != null ? ((CosmosString)feedRangeState.State.Value).Value : null, range: ((FeedRangeEpk)feedRangeState.FeedRange).Range); activeParallelContinuationTokens.Add(parallelContinuationToken); } } IEnumerable <CosmosElement> cosmosElementContinuationTokens = activeParallelContinuationTokens .Select(token => ParallelContinuationToken.ToCosmosElement(token)); CosmosArray cosmosElementParallelContinuationTokens = CosmosArray.Create(cosmosElementContinuationTokens); queryState = new QueryState(cosmosElementParallelContinuationTokens); } QueryPage crossPartitionQueryPage = new QueryPage( backendQueryPage.Documents, backendQueryPage.RequestCharge, backendQueryPage.ActivityId, backendQueryPage.ResponseLengthInBytes, backendQueryPage.CosmosQueryExecutionInfo, backendQueryPage.DisallowContinuationTokenMessage, queryState); this.Current = TryCatch <QueryPage> .FromResult(crossPartitionQueryPage); return(true); }
// 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. public async ValueTask <bool> MoveNextAsync() { this.cancellationToken.ThrowIfCancellationRequested(); if (!await this.crossPartitionRangePageAsyncEnumerator.MoveNextAsync()) { this.Current = default; return(false); } TryCatch <CrossPartitionPage <QueryPage, QueryState> > currentCrossPartitionPage = this.crossPartitionRangePageAsyncEnumerator.Current; if (currentCrossPartitionPage.Failed) { this.Current = TryCatch <QueryPage> .FromException(currentCrossPartitionPage.Exception); return(true); } CrossPartitionPage <QueryPage, QueryState> crossPartitionPageResult = currentCrossPartitionPage.Result; QueryPage backendQueryPage = crossPartitionPageResult.Page; CrossPartitionState <QueryState> crossPartitionState = crossPartitionPageResult.State; QueryState queryState; if (crossPartitionState == null) { queryState = null; } else { // Left most and any non null continuations List <(PartitionKeyRange, QueryState)> rangesAndStates = crossPartitionState.Value.OrderBy(tuple => tuple.Item1, PartitionKeyRangeComparer.Singleton).ToList(); List <ParallelContinuationToken> activeParallelContinuationTokens = new List <ParallelContinuationToken>(); for (int i = 0; i < rangesAndStates.Count; i++) { this.cancellationToken.ThrowIfCancellationRequested(); (PartitionKeyRange range, QueryState state) = rangesAndStates[i]; if ((i == 0) || (state != null)) { ParallelContinuationToken parallelContinuationToken = new ParallelContinuationToken( token: state != null ? ((CosmosString)state.Value).Value : null, range: range.ToRange()); activeParallelContinuationTokens.Add(parallelContinuationToken); } } IEnumerable <CosmosElement> cosmosElementContinuationTokens = activeParallelContinuationTokens .Select(token => ParallelContinuationToken.ToCosmosElement(token)); CosmosArray cosmosElementParallelContinuationTokens = CosmosArray.Create(cosmosElementContinuationTokens); queryState = new QueryState(cosmosElementParallelContinuationTokens); } QueryPage crossPartitionQueryPage = new QueryPage( backendQueryPage.Documents, backendQueryPage.RequestCharge, backendQueryPage.ActivityId, backendQueryPage.ResponseLengthInBytes, backendQueryPage.CosmosQueryExecutionInfo, backendQueryPage.DisallowContinuationTokenMessage, queryState); this.Current = TryCatch <QueryPage> .FromResult(crossPartitionQueryPage); return(true); }