public static TryCatch <ParallelContinuationToken> TryCreateFromCosmosElement(CosmosElement cosmosElement) { if (!(cosmosElement is CosmosObject cosmosObject)) { return(TryCatch <ParallelContinuationToken> .FromException( new MalformedContinuationTokenException($"{nameof(ParallelContinuationToken)} is not an object: {cosmosElement}"))); } if (!cosmosObject.TryGetValue(PropertyNames.Token, out CosmosElement rawToken)) { return(TryCatch <ParallelContinuationToken> .FromException( new MalformedContinuationTokenException($"{nameof(ParallelContinuationToken)} is missing field: '{PropertyNames.Token}': {cosmosElement}"))); } string token; if (rawToken is CosmosString rawTokenString) { token = rawTokenString.Value; } else { token = null; } if (!cosmosObject.TryGetValue(PropertyNames.Range, out CosmosObject rawRange)) { return(TryCatch <ParallelContinuationToken> .FromException( new MalformedContinuationTokenException($"{nameof(ParallelContinuationToken)} is missing field: '{PropertyNames.Range}': {cosmosElement}"))); } if (!rawRange.TryGetValue(PropertyNames.Min, out CosmosString rawMin)) { return(TryCatch <ParallelContinuationToken> .FromException( new MalformedContinuationTokenException($"{nameof(ParallelContinuationToken)} is missing field: '{PropertyNames.Min}': {cosmosElement}"))); } string min = rawMin.Value; if (!rawRange.TryGetValue(PropertyNames.Max, out CosmosString rawMax)) { return(TryCatch <ParallelContinuationToken> .FromException( new MalformedContinuationTokenException($"{nameof(ParallelContinuationToken)} is missing field: '{PropertyNames.Max}': {cosmosElement}"))); } string max = rawMax.Value; Range <string> range = new Documents.Routing.Range <string>(min, max, isMinInclusive: true, isMaxInclusive: false); ParallelContinuationToken parallelContinuationToken = new ParallelContinuationToken(token, range); return(TryCatch <ParallelContinuationToken> .FromResult(parallelContinuationToken)); }
public static CosmosElement ToCosmosElement(ParallelContinuationToken parallelContinuationToken) { CosmosElement token = parallelContinuationToken.Token == null ? (CosmosElement)CosmosNull.Create() : (CosmosElement)CosmosString.Create(parallelContinuationToken.Token); return(CosmosObject.Create( new Dictionary <string, CosmosElement>() { { ParallelContinuationToken.PropertyNames.Token, token }, { ParallelContinuationToken.PropertyNames.Range, CosmosObject.Create( new Dictionary <string, CosmosElement>() { { PropertyNames.Min, CosmosString.Create(parallelContinuationToken.Range.Min) }, { PropertyNames.Max, CosmosString.Create(parallelContinuationToken.Range.Max) } }) }, })); }
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); }
private static TryCatch <CrossFeedRangeState <QueryState> > MonadicExtractState( CosmosElement continuationToken, IReadOnlyList <FeedRangeEpk> ranges) { if (continuationToken == null) { // Full fan out to the ranges with null continuations CrossFeedRangeState <QueryState> fullFanOutState = new CrossFeedRangeState <QueryState>(ranges.Select(range => new FeedRangeState <QueryState>(range, (QueryState)null)).ToArray()); return(TryCatch <CrossFeedRangeState <QueryState> > .FromResult(fullFanOutState)); } if (!(continuationToken is CosmosArray parallelContinuationTokenListRaw)) { return(TryCatch <CrossFeedRangeState <QueryState> > .FromException( new MalformedContinuationTokenException( $"Invalid format for continuation token {continuationToken} for {nameof(ParallelCrossPartitionQueryPipelineStage)}"))); } if (parallelContinuationTokenListRaw.Count == 0) { return(TryCatch <CrossFeedRangeState <QueryState> > .FromException( new MalformedContinuationTokenException( $"Invalid format for continuation token {continuationToken} for {nameof(ParallelCrossPartitionQueryPipelineStage)}"))); } List <ParallelContinuationToken> parallelContinuationTokens = new List <ParallelContinuationToken>(); foreach (CosmosElement parallelContinuationTokenRaw in parallelContinuationTokenListRaw) { TryCatch <ParallelContinuationToken> tryCreateParallelContinuationToken = ParallelContinuationToken.TryCreateFromCosmosElement(parallelContinuationTokenRaw); if (tryCreateParallelContinuationToken.Failed) { return(TryCatch <CrossFeedRangeState <QueryState> > .FromException( tryCreateParallelContinuationToken.Exception)); } parallelContinuationTokens.Add(tryCreateParallelContinuationToken.Result); } TryCatch <PartitionMapping <ParallelContinuationToken> > partitionMappingMonad = PartitionMapper.MonadicGetPartitionMapping( ranges, parallelContinuationTokens); if (partitionMappingMonad.Failed) { return(TryCatch <CrossFeedRangeState <QueryState> > .FromException( partitionMappingMonad.Exception)); } PartitionMapping <ParallelContinuationToken> partitionMapping = partitionMappingMonad.Result; List <FeedRangeState <QueryState> > feedRangeStates = new List <FeedRangeState <QueryState> >(); List <IReadOnlyDictionary <FeedRangeEpk, ParallelContinuationToken> > rangesToInitialize = new List <IReadOnlyDictionary <FeedRangeEpk, ParallelContinuationToken> >() { // Skip all the partitions left of the target range, since they have already been drained fully. partitionMapping.TargetMapping, partitionMapping.MappingRightOfTarget, }; foreach (IReadOnlyDictionary <FeedRangeEpk, ParallelContinuationToken> rangeToInitalize in rangesToInitialize) { foreach (KeyValuePair <FeedRangeEpk, ParallelContinuationToken> kvp in rangeToInitalize) { FeedRangeState <QueryState> feedRangeState = new FeedRangeState <QueryState>(kvp.Key, kvp.Value?.Token != null ? new QueryState(CosmosString.Create(kvp.Value.Token)) : null); feedRangeStates.Add(feedRangeState); } } CrossFeedRangeState <QueryState> crossPartitionState = new CrossFeedRangeState <QueryState>(feedRangeStates.ToArray()); return(TryCatch <CrossFeedRangeState <QueryState> > .FromResult(crossPartitionState)); }
// 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); }