public async ValueTask <bool> MoveNextAsync() { this.cancellationToken.ThrowIfCancellationRequested(); if (!await this.crossPartitionEnumerator.MoveNextAsync()) { this.Current = default; return(false); } TryCatch <CrossPartitionPage <ReadFeedPage, ReadFeedState> > monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { this.Current = TryCatch <ReadFeedPage> .FromException(monadicCrossPartitionPage.Exception); return(true); } CrossPartitionPage <ReadFeedPage, ReadFeedState> crossPartitionPage = monadicCrossPartitionPage.Result; ReadFeedPage backendPage = crossPartitionPage.Page; CrossPartitionState <ReadFeedState> crossPartitionState = crossPartitionPage.State; ReadFeedState state; if (crossPartitionState != null) { IReadOnlyList <(FeedRangeInternal, ReadFeedState)> rangesAndStates = crossPartitionState.Value; List <CosmosElement> changeFeedContinuationTokens = new List <CosmosElement>(); foreach ((FeedRangeInternal range, ReadFeedState readFeedState) in rangesAndStates) { this.cancellationToken.ThrowIfCancellationRequested(); ReadFeedContinuationToken readFeedContinuationToken = new ReadFeedContinuationToken( range, readFeedState); CosmosElement cosmosElementChangeFeedContinuationToken = ReadFeedContinuationToken.ToCosmosElement(readFeedContinuationToken); changeFeedContinuationTokens.Add(cosmosElementChangeFeedContinuationToken); } CosmosArray cosmosElementTokens = CosmosArray.Create(changeFeedContinuationTokens); state = new ReadFeedState(cosmosElementTokens); } else { state = null; } ReadFeedPage compositePage = new ReadFeedPage(backendPage.Content, backendPage.RequestCharge, backendPage.ActivityId, backendPage.Diagnostics, state); this.Current = TryCatch <ReadFeedPage> .FromResult(compositePage); return(true); }
public override TryCatch <CrossPartitionState <ChangeFeedState> > Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) { ChangeFeedState state = ChangeFeedState.Continuation(CosmosString.Create(startFromContinuationAndFeedRange.Etag)); List <(FeedRangeInternal, ChangeFeedState)> rangesAndStates = new List <(FeedRangeInternal, ChangeFeedState)>() { (startFromContinuationAndFeedRange.FeedRange, state) }; CrossPartitionState <ChangeFeedState> crossPartitionState = new CrossPartitionState <ChangeFeedState>(rangesAndStates); return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromResult(crossPartitionState)); }
public override TryCatch <CrossPartitionState <ChangeFeedState> > Visit(ChangeFeedStartFromBeginning startFromBeginning) { ChangeFeedState state = ChangeFeedState.Beginning(); List <(FeedRangeInternal, ChangeFeedState)> rangesAndStates = new List <(FeedRangeInternal, ChangeFeedState)>() { (startFromBeginning.FeedRange, state) }; CrossPartitionState <ChangeFeedState> crossPartitionState = new CrossPartitionState <ChangeFeedState>(rangesAndStates); return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromResult(crossPartitionState)); }
public static TryCatch <IQueryPipelineStage> MonadicCreate( IDocumentContainer documentContainer, SqlQuerySpec sqlQuerySpec, IReadOnlyList <FeedRangeEpk> targetRanges, Cosmos.PartitionKey?partitionKey, int pageSize, int maxConcurrency, CosmosElement continuationToken, CancellationToken cancellationToken) { if (targetRanges == null) { throw new ArgumentNullException(nameof(targetRanges)); } if (targetRanges.Count == 0) { throw new ArgumentException($"{nameof(targetRanges)} must have some elements"); } if (pageSize <= 0) { throw new ArgumentOutOfRangeException(nameof(pageSize)); } TryCatch <CrossPartitionState <QueryState> > monadicExtractState = MonadicExtractState(continuationToken, targetRanges); if (monadicExtractState.Failed) { return(TryCatch <IQueryPipelineStage> .FromException(monadicExtractState.Exception)); } CrossPartitionState <QueryState> state = monadicExtractState.Result; CrossPartitionRangePageAsyncEnumerator <QueryPage, QueryState> crossPartitionPageEnumerator = new CrossPartitionRangePageAsyncEnumerator <QueryPage, QueryState>( documentContainer, ParallelCrossPartitionQueryPipelineStage.MakeCreateFunction(documentContainer, sqlQuerySpec, pageSize, partitionKey, cancellationToken), comparer: Comparer.Singleton, maxConcurrency, state: state, cancellationToken: cancellationToken); ParallelCrossPartitionQueryPipelineStage stage = new ParallelCrossPartitionQueryPipelineStage(crossPartitionPageEnumerator, cancellationToken); return(TryCatch <IQueryPipelineStage> .FromResult(stage)); }
public override TryCatch <CrossPartitionState <ChangeFeedState> > Visit(ChangeFeedStartFromContinuation startFromContinuation) { string continuationToken = startFromContinuation.Continuation; TryCatch <CosmosArray> monadicCosmosArray = CosmosArray.Monadic.Parse(continuationToken); if (monadicCosmosArray.Failed) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"Array expected for change feed continuation token: {continuationToken}.", innerException : monadicCosmosArray.Exception))); } CosmosArray cosmosArray = monadicCosmosArray.Result; if (cosmosArray.Count == 0) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"non empty array expected for change feed continuation token: {continuationToken}."))); } List <(FeedRangeInternal, ChangeFeedState)> rangeAndStates = new List <(FeedRangeInternal, ChangeFeedState)>(); foreach (CosmosElement arrayItem in cosmosArray) { TryCatch <ChangeFeedContinuationToken> monadicChangeFeedContinuationToken = ChangeFeedContinuationToken.MonadicConvertFromCosmosElement(arrayItem); if (monadicChangeFeedContinuationToken.Failed) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"Failed to parse change feed continuation token: {continuationToken}.", innerException : monadicChangeFeedContinuationToken.Exception))); } ChangeFeedContinuationToken changeFeedContinuationToken = monadicChangeFeedContinuationToken.Result; rangeAndStates.Add((changeFeedContinuationToken.Range, changeFeedContinuationToken.State)); } CrossPartitionState <ChangeFeedState> crossPartitionState = new CrossPartitionState <ChangeFeedState>(rangeAndStates); return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromResult(crossPartitionState)); }
public CrossPartitionPage(TBackendPage backendEndPage, CrossPartitionState <TBackendState> state) : base(state) { this.Page = backendEndPage; }
public async ValueTask <bool> MoveNextAsync() { this.cancellationToken.ThrowIfCancellationRequested(); if (this.bufferedException.HasValue) { this.Current = this.bufferedException.Value; this.bufferedException = null; return(true); } if (!await this.crossPartitionEnumerator.MoveNextAsync()) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } TryCatch <CrossPartitionPage <ChangeFeedPage, ChangeFeedState> > monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { this.Current = TryCatch <ChangeFeedPage> .FromException(monadicCrossPartitionPage.Exception); return(true); } CrossPartitionPage <ChangeFeedPage, ChangeFeedState> crossPartitionPage = monadicCrossPartitionPage.Result; ChangeFeedPage backendPage = crossPartitionPage.Page; if (backendPage is ChangeFeedNotModifiedPage) { // Keep draining the cross partition enumerator until // We get a non 304 page or we loop back to the same range or run into an exception FeedRangeInternal originalRange = this.crossPartitionEnumerator.CurrentRange; double totalRequestCharge = backendPage.RequestCharge; do { if (!await this.crossPartitionEnumerator.MoveNextAsync()) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { // Buffer the exception, since we need to return the request charge so far. this.bufferedException = TryCatch <ChangeFeedPage> .FromException(monadicCrossPartitionPage.Exception); } else { crossPartitionPage = monadicCrossPartitionPage.Result; backendPage = crossPartitionPage.Page; totalRequestCharge += backendPage.RequestCharge; } }while (!(backendPage is ChangeFeedSuccessPage || this.crossPartitionEnumerator.CurrentRange.Equals(originalRange) || this.bufferedException.HasValue)); // Create a page with the aggregated request charge if (backendPage is ChangeFeedSuccessPage changeFeedSuccessPage) { backendPage = new ChangeFeedSuccessPage( changeFeedSuccessPage.Content, totalRequestCharge, changeFeedSuccessPage.ActivityId, changeFeedSuccessPage.State); } else { backendPage = new ChangeFeedNotModifiedPage( totalRequestCharge, backendPage.ActivityId, backendPage.State); } } CrossPartitionState <ChangeFeedState> crossPartitionState = crossPartitionPage.State; List <CosmosElement> changeFeedContinuationTokens = new List <CosmosElement>(); foreach ((FeedRangeInternal range, ChangeFeedState state)rangeAndState in crossPartitionState.Value) { ChangeFeedContinuationToken changeFeedContinuationToken = new ChangeFeedContinuationToken( rangeAndState.range, rangeAndState.state); CosmosElement cosmosElementChangeFeedContinuationToken = ChangeFeedContinuationToken.ToCosmosElement(changeFeedContinuationToken); changeFeedContinuationTokens.Add(cosmosElementChangeFeedContinuationToken); } CosmosArray cosmosElementTokens = CosmosArray.Create(changeFeedContinuationTokens); ChangeFeedState state = ChangeFeedState.Continuation(cosmosElementTokens); ChangeFeedPage compositePage; if (backendPage is ChangeFeedSuccessPage successPage) { compositePage = new ChangeFeedSuccessPage( successPage.Content, successPage.RequestCharge, successPage.ActivityId, state); } else { compositePage = new ChangeFeedNotModifiedPage( backendPage.RequestCharge, backendPage.ActivityId, state); } this.Current = TryCatch <ChangeFeedPage> .FromResult(compositePage); 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); }
private static TryCatch <CrossPartitionState <QueryState> > MonadicExtractState( CosmosElement continuationToken, IReadOnlyList <PartitionKeyRange> ranges) { if (continuationToken == null) { // Full fan out to the ranges with null continuations CrossPartitionState <QueryState> fullFanOutState = new CrossPartitionState <QueryState>(ranges.Select(range => (range, (QueryState)null)).ToArray()); return(TryCatch <CrossPartitionState <QueryState> > .FromResult(fullFanOutState)); } if (!(continuationToken is CosmosArray parallelContinuationTokenListRaw)) { return(TryCatch <CrossPartitionState <QueryState> > .FromException( new MalformedContinuationTokenException( $"Invalid format for continuation token {continuationToken} for {nameof(ParallelCrossPartitionQueryPipelineStage)}"))); } if (parallelContinuationTokenListRaw.Count == 0) { return(TryCatch <CrossPartitionState <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 <CrossPartitionState <QueryState> > .FromException( tryCreateParallelContinuationToken.Exception)); } parallelContinuationTokens.Add(tryCreateParallelContinuationToken.Result); } TryCatch <PartitionMapping <ParallelContinuationToken> > partitionMappingMonad = PartitionMapper.MonadicGetPartitionMapping( ranges, parallelContinuationTokens); if (partitionMappingMonad.Failed) { return(TryCatch <CrossPartitionState <QueryState> > .FromException( partitionMappingMonad.Exception)); } PartitionMapping <ParallelContinuationToken> partitionMapping = partitionMappingMonad.Result; List <(PartitionKeyRange, QueryState)> rangesAndStates = new List <(PartitionKeyRange, QueryState)>(); List <IReadOnlyDictionary <PartitionKeyRange, ParallelContinuationToken> > rangesToInitialize = new List <IReadOnlyDictionary <PartitionKeyRange, ParallelContinuationToken> >() { // Skip all the partitions left of the target range, since they have already been drained fully. partitionMapping.TargetPartition, partitionMapping.PartitionsRightOfTarget, }; foreach (IReadOnlyDictionary <PartitionKeyRange, ParallelContinuationToken> rangeToInitalize in rangesToInitialize) { foreach (KeyValuePair <PartitionKeyRange, ParallelContinuationToken> kvp in rangeToInitalize) { (PartitionKeyRange, QueryState)rangeAndState = (kvp.Key, kvp.Value?.Token != null ? new QueryState(CosmosString.Create(kvp.Value.Token)) : null); rangesAndStates.Add(rangeAndState); } } CrossPartitionState <QueryState> crossPartitionState = new CrossPartitionState <QueryState>(rangesAndStates); return(TryCatch <CrossPartitionState <QueryState> > .FromResult(crossPartitionState)); }