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) }
                    })
                },
            }));
        }
Exemple #3
0
        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);
        }
Exemple #4
0
        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);
        }