Пример #1
0
        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);
        }
Пример #2
0
            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));
            }
Пример #3
0
            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));
        }
Пример #5
0
            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));
            }
Пример #6
0
 public CrossPartitionPage(TBackendPage backendEndPage, CrossPartitionState <TBackendState> state)
     : base(state)
 {
     this.Page = backendEndPage;
 }
Пример #7
0
        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));
        }