Пример #1
0
        internal async Task <List <PartitionKeyRange> > GetTargetPartitionKeyRangesAsync(string collectionResourceId, List <Range <string> > providedRanges)
        {
            if (string.IsNullOrEmpty(nameof(collectionResourceId)))
            {
                throw new ArgumentNullException();
            }

            if (providedRanges == null || !providedRanges.Any())
            {
                throw new ArgumentNullException(nameof(providedRanges));
            }

            IRoutingMapProvider routingMapProvider = await this.Client.GetRoutingMapProviderAsync();

            List <PartitionKeyRange> ranges = await routingMapProvider.TryGetOverlappingRangesAsync(collectionResourceId, providedRanges);

            if (ranges == null && PathsHelper.IsNameBased(this.ResourceLink))
            {
                // Refresh the cache and don't try to re-resolve collection as it is not clear what already
                // happened based on previously resolved collection rid.
                // Return NotFoundException this time. Next query will succeed.
                // This can only happen if collection is deleted/created with same name and client was not restarted
                // in between.
                CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

                collectionCache.Refresh(this.ResourceLink);
            }

            if (ranges == null)
            {
                throw new NotFoundException($"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: GetTargetPartitionKeyRanges(collectionResourceId:{collectionResourceId}, providedRanges: {string.Join(",", providedRanges)} failed due to stale cache");
            }

            return(ranges);
        }
 public static async Task <List <PartitionKeyRange> > GetReplacementRangesAsync(PartitionKeyRange targetRange, IRoutingMapProvider routingMapProvider, string collectionRid)
 {
     return((await routingMapProvider.TryGetOverlappingRangesAsync(
                 collectionRid,
                 targetRange.ToRange(),
                 NoOpTrace.Singleton,
                 forceRefresh: true)).ToList());
 }
Пример #3
0
        internal override async Task <IEnumerable <string> > GetPartitionKeyRangesAsync(
            IRoutingMapProvider routingMapProvider,
            string containerRid,
            Documents.PartitionKeyDefinition partitionKeyDefinition,
            CancellationToken cancellationToken)
        {
            IReadOnlyList <Documents.PartitionKeyRange> partitionKeyRanges = await routingMapProvider.TryGetOverlappingRangesAsync(containerRid, this.Range, forceRefresh : false);

            return(partitionKeyRanges.Select(partitionKeyRange => partitionKeyRange.Id));
        }
Пример #4
0
        private async Task <IReadOnlyList <PartitionKeyRange> > GetPartitionKeyRanges(ContainerProperties container)
        {
            Range <string> fullRange = new Range <string>(
                PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                true,
                false);
            IRoutingMapProvider routingMapProvider = await this.Client.DocumentClient.GetPartitionKeyRangeCacheAsync();

            Assert.IsNotNull(routingMapProvider);

            IReadOnlyList <PartitionKeyRange> ranges = await routingMapProvider.TryGetOverlappingRangesAsync(container.ResourceId, fullRange);

            return(ranges);
        }
        public static async Task <PartitionKeyRange> TryGetRangeByEffectivePartitionKey(
            this IRoutingMapProvider routingMapProvider,
            string collectionResourceId,
            string effectivePartitionKey)
        {
            IReadOnlyList <PartitionKeyRange> ranges = await routingMapProvider.TryGetOverlappingRangesAsync(
                collectionResourceId,
                Range <string> .GetPointRange(effectivePartitionKey));

            if (ranges == null)
            {
                return(null);
            }

            return(ranges.Single());
        }
Пример #6
0
        private DocumentServiceResponse ReadDocumentFeedRequest(DocumentClient client, string collectionId, INameValueCollection headers)
        {
            DocumentServiceRequest request = DocumentServiceRequest.Create(OperationType.ReadFeed, collectionId, ResourceType.Document, AuthorizationTokenType.PrimaryMasterKey, headers);

            Range <string> fullRange = new Range <string>(
                PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                true,
                false);
            IRoutingMapProvider routingMapProvider   = client.GetPartitionKeyRangeCacheAsync().Result;
            IReadOnlyList <PartitionKeyRange> ranges = routingMapProvider.TryGetOverlappingRangesAsync(collectionId, fullRange).Result;

            request.RouteTo(new PartitionKeyRangeIdentity(collectionId, ranges.First().Id));

            var response = client.ReadFeedAsync(request, null).Result;

            return(response);
        }
Пример #7
0
        protected async Task <List <PartitionKeyRange> > GetReplacementRangesAsync(PartitionKeyRange targetRange, string collectionRid)
        {
            IRoutingMapProvider routingMapProvider = await this.Client.GetRoutingMapProviderAsync();

            List <PartitionKeyRange> replacementRanges = (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, targetRange.ToRange(), true)).ToList();
            string replaceMinInclusive = replacementRanges.First().MinInclusive;
            string replaceMaxExclusive = replacementRanges.Last().MaxExclusive;

            if (!replaceMinInclusive.Equals(targetRange.MinInclusive, StringComparison.Ordinal) || !replaceMaxExclusive.Equals(targetRange.MaxExclusive, StringComparison.Ordinal))
            {
                throw new InternalServerErrorException(string.Format(
                                                           CultureInfo.InvariantCulture,
                                                           "Target range and Replacement range has mismatched min/max. Target range: [{0}, {1}). Replacement range: [{2}, {3}).",
                                                           targetRange.MinInclusive,
                                                           targetRange.MaxExclusive,
                                                           replaceMinInclusive,
                                                           replaceMaxExclusive));
            }

            return(replacementRanges);
        }
        public static async Task <List <PartitionKeyRange> > TryGetOverlappingRangesAsync(
            this IRoutingMapProvider routingMapProvider,
            string collectionResourceId,
            IList <Range <string> > sortedRanges,
            bool forceRefresh = false)
        {
            if (!IsSortedAndNonOverlapping(sortedRanges))
            {
                throw new ArgumentException("sortedRanges");
            }

            List <PartitionKeyRange> targetRanges = new List <PartitionKeyRange>();
            int currentProvidedRange = 0;

            while (currentProvidedRange < sortedRanges.Count)
            {
                if (sortedRanges[currentProvidedRange].IsEmpty)
                {
                    currentProvidedRange++;
                    continue;
                }

                Range <string> queryRange;
                if (targetRanges.Count > 0)
                {
                    string left = Max(
                        targetRanges[targetRanges.Count - 1].MaxExclusive,
                        sortedRanges[currentProvidedRange].Min);

                    bool leftInclusive = string.CompareOrdinal(left, sortedRanges[currentProvidedRange].Min) == 0
                                             ? sortedRanges[currentProvidedRange].IsMinInclusive
                                             : false;

                    queryRange = new Range <string>(
                        left,
                        sortedRanges[currentProvidedRange].Max,
                        leftInclusive,
                        sortedRanges[currentProvidedRange].IsMaxInclusive);
                }
                else
                {
                    queryRange = sortedRanges[currentProvidedRange];
                }

                IReadOnlyList <PartitionKeyRange> overlappingRanges =
                    await routingMapProvider.TryGetOverlappingRangesAsync(collectionResourceId, queryRange, forceRefresh);

                if (overlappingRanges == null)
                {
                    return(null);
                }

                targetRanges.AddRange(overlappingRanges);

                Range <string> lastKnownTargetRange = targetRanges[targetRanges.Count - 1].ToRange();
                while (currentProvidedRange < sortedRanges.Count &&
                       Range <string> .MaxComparer.Instance.Compare(sortedRanges[currentProvidedRange], lastKnownTargetRange) <= 0)
                {
                    currentProvidedRange++;
                }
            }

            return(targetRanges);
        }
        public virtual async Task <bool> TryAddPartitionKeyRangeToContinuationTokenAsync(
            INameValueCollection backendResponseHeaders,
            IReadOnlyList <Range <string> > providedPartitionKeyRanges,
            IRoutingMapProvider routingMapProvider,
            string collectionRid,
            ResolvedRangeInfo resolvedRangeInfo,
            RntdbEnumerationDirection direction = RntdbEnumerationDirection.Forward)
        {
            Debug.Assert(resolvedRangeInfo.ResolvedRange != null, "ResolvedRange can't be null");

            PartitionKeyRange currentRange = resolvedRangeInfo.ResolvedRange;

            // IF : Split happened, or already had multiple target ranges in the continuation
            if (resolvedRangeInfo.ContinuationTokens != null && resolvedRangeInfo.ContinuationTokens.Count > 1)
            {
                if (!string.IsNullOrEmpty(backendResponseHeaders[HttpConstants.HttpHeaders.Continuation]))
                {
                    resolvedRangeInfo.ContinuationTokens[0].Token = backendResponseHeaders[HttpConstants.HttpHeaders.Continuation];
                }
                else
                {
                    resolvedRangeInfo.ContinuationTokens.RemoveAt(0);
                }

                backendResponseHeaders[HttpConstants.HttpHeaders.Continuation] = JsonConvert.SerializeObject(resolvedRangeInfo.ContinuationTokens);
            }
            else
            {
                //// ELSE: Single target Range was provided, and no split happened

                PartitionKeyRange rangeToUse = currentRange;

                // We only need to get the next range if we have to
                if (string.IsNullOrEmpty(backendResponseHeaders[HttpConstants.HttpHeaders.Continuation]))
                {
                    if (direction == RntdbEnumerationDirection.Reverse)
                    {
                        rangeToUse = PartitionRoutingHelper.MinBefore(
                            (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, providedPartitionKeyRanges.Single())).ToList(),
                            currentRange);
                    }
                    else
                    {
                        Range <string> nextProvidedRange = PartitionRoutingHelper.MinAfter(
                            providedPartitionKeyRanges,
                            currentRange.ToRange(),
                            Range <string> .MaxComparer.Instance);

                        if (nextProvidedRange == null)
                        {
                            return(true);
                        }

                        string max = string.CompareOrdinal(nextProvidedRange.Min, currentRange.MaxExclusive) > 0
                             ? nextProvidedRange.Min
                             : currentRange.MaxExclusive;

                        if (string.CompareOrdinal(max, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey) == 0)
                        {
                            return(true);
                        }

                        PartitionKeyRange nextRange = await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, max);

                        if (nextRange == null)
                        {
                            return(false);
                        }

                        rangeToUse = nextRange;
                    }
                }

                if (rangeToUse != null)
                {
                    backendResponseHeaders[HttpConstants.HttpHeaders.Continuation] = PartitionRoutingHelper.AddPartitionKeyRangeToContinuationToken(
                        backendResponseHeaders[HttpConstants.HttpHeaders.Continuation],
                        rangeToUse);
                }
            }

            return(true);
        }
        /// <summary>
        /// Gets <see cref="PartitionKeyRange"/> instance which corresponds to <paramref name="rangeFromContinuationToken"/>
        /// </summary>
        /// <param name="providedPartitionKeyRanges"></param>
        /// <param name="routingMapProvider"></param>
        /// <param name="collectionRid"></param>
        /// <param name="rangeFromContinuationToken"></param>
        /// <param name="suppliedTokens"></param>
        /// <param name="direction"></param>
        /// <returns>null if collection with specified <paramref name="collectionRid"/> doesn't exist, which potentially means
        /// that collection was resolved to outdated Rid by name. Also null can be returned if <paramref name="rangeFromContinuationToken"/>
        /// is not found - this means it was split.
        /// </returns>
        public virtual async Task <ResolvedRangeInfo> TryGetTargetRangeFromContinuationTokenRangeAsync(
            IReadOnlyList <Range <string> > providedPartitionKeyRanges,
            IRoutingMapProvider routingMapProvider,
            string collectionRid,
            Range <string> rangeFromContinuationToken,
            List <CompositeContinuationToken> suppliedTokens,
            RntdbEnumerationDirection direction = RntdbEnumerationDirection.Forward)
        {
            // For queries such as "SELECT * FROM root WHERE false",
            // we will have empty ranges and just forward the request to the first partition
            if (providedPartitionKeyRanges.Count == 0)
            {
                return(new ResolvedRangeInfo(
                           await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(
                               collectionRid,
                               PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey),
                           suppliedTokens));
            }

            // Initially currentRange will be empty
            if (rangeFromContinuationToken.IsEmpty)
            {
                if (direction == RntdbEnumerationDirection.Reverse)
                {
                    PartitionKeyRange lastPartitionKeyRange = (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, providedPartitionKeyRanges.Single())).Last();
                    return(new ResolvedRangeInfo(
                               lastPartitionKeyRange,
                               suppliedTokens));
                }

                Range <string> minimumRange = PartitionRoutingHelper.Min(
                    providedPartitionKeyRanges,
                    Range <string> .MinComparer.Instance);

                return(new ResolvedRangeInfo(
                           await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, minimumRange.Min),
                           suppliedTokens));
            }

            PartitionKeyRange targetPartitionKeyRange = await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, rangeFromContinuationToken.Min);

            if (targetPartitionKeyRange == null)
            {
                return(new ResolvedRangeInfo(null, suppliedTokens));
            }

            if (!rangeFromContinuationToken.Equals(targetPartitionKeyRange.ToRange()))
            {
                // Cannot find target range. Either collection was resolved incorrectly or the range was split
                List <PartitionKeyRange> replacedRanges = (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, rangeFromContinuationToken, true)).ToList();

                if (replacedRanges == null || replacedRanges.Count < 1)
                {
                    return(new ResolvedRangeInfo(null, null));
                }
                else
                {
                    if (!(replacedRanges[0].MinInclusive.Equals(rangeFromContinuationToken.Min) && replacedRanges[replacedRanges.Count - 1].MaxExclusive.Equals(rangeFromContinuationToken.Max)))
                    {
                        return(new ResolvedRangeInfo(null, null));
                    }
                }

                if (direction == RntdbEnumerationDirection.Reverse)
                {
                    replacedRanges.Reverse();
                }

                List <CompositeContinuationToken> continuationTokensToBePersisted = null;

                if (suppliedTokens != null && suppliedTokens.Count > 0)
                {
                    continuationTokensToBePersisted = new List <CompositeContinuationToken>(replacedRanges.Count + suppliedTokens.Count - 1);

                    foreach (PartitionKeyRange partitionKeyRange in replacedRanges)
                    {
                        CompositeContinuationToken token = (CompositeContinuationToken)suppliedTokens[0].ShallowCopy();
                        token.Range = partitionKeyRange.ToRange();
                        continuationTokensToBePersisted.Add(token);
                    }

                    continuationTokensToBePersisted.AddRange(suppliedTokens.Skip(1));
                }

                return(new ResolvedRangeInfo(replacedRanges[0], continuationTokensToBePersisted));
            }

            return(new ResolvedRangeInfo(targetPartitionKeyRange, suppliedTokens));
        }
        public static async Task <List <PartitionKeyRange> > TryGetOverlappingRangesAsync(
            this IRoutingMapProvider routingMapProvider,
            string collectionResourceId,
            IEnumerable <Range <string> > sortedRanges,
            bool forceRefresh = false)
        {
            if (sortedRanges == null)
            {
                throw new ArgumentNullException(nameof(sortedRanges));
            }

            // Remove the duplicates
            SortedSet <Range <string> > distinctSortedRanges = new SortedSet <Range <string> >(sortedRanges, Range <string> .MinComparer.Instance);

            // Make sure there is no overlap
            if (!IRoutingMapProviderExtensions.IsNonOverlapping(distinctSortedRanges))
            {
                throw new ArgumentException($"{nameof(sortedRanges)} had overlaps.");
            }

            // For each range try to figure out what PartitionKeyRanges it spans.
            List <PartitionKeyRange> targetRanges = new List <PartitionKeyRange>();

            foreach (Range <string> range in sortedRanges)
            {
                // if the range is empty then it by definition does not span any ranges.
                if (range.IsEmpty)
                {
                    continue;
                }

                // If current range already is covered by the most recently added PartitionKeyRange, then we can skip it
                // (to avoid duplicates).
                if ((targetRanges.Count != 0) && (Range <string> .MaxComparer.Instance.Compare(range, targetRanges.Last().ToRange()) <= 0))
                {
                    continue;
                }

                // Calculate what range to look up.
                Range <string> queryRange;
                if (targetRanges.Count == 0)
                {
                    // If there are no existing partition key ranges,
                    // then we take the first to get the ball rolling.
                    queryRange = range;
                }
                else
                {
                    // We don't want to double count a partition key range
                    // So we form a new range where
                    // * left of the range is Max(lastPartitionKeyRange.Right(), currentRange.Left())
                    // * right is just the right of the currentRange.
                    // That way if the current range overlaps with the partition key range it won't double count it when doing:
                    //  routingMapProvider.TryGetOverlappingRangesAsync
                    string left          = IRoutingMapProviderExtensions.Max(targetRanges.Last().MaxExclusive, range.Min);
                    bool   leftInclusive = string.CompareOrdinal(left, range.Min) == 0 ? range.IsMinInclusive : false;
                    queryRange = new Range <string>(
                        left,
                        range.Max,
                        leftInclusive,
                        range.IsMaxInclusive);
                }

                IReadOnlyList <PartitionKeyRange> overlappingRanges = await routingMapProvider.TryGetOverlappingRangesAsync(
                    collectionResourceId,
                    queryRange,
                    forceRefresh);

                if (overlappingRanges == null)
                {
                    // null means we weren't able to find the overlapping ranges.
                    // This is due to a stale cache.
                    // It is the caller's responsiblity to recall this method with forceRefresh = true
                    return(null);

                    // Design note: It would be better if this method just returned a bool and followed the standard TryGet Pattern.
                    // It would be even better to remove the forceRefresh flag just replace it with a non TryGet method call.
                }

                targetRanges.AddRange(overlappingRanges);
            }

            return(targetRanges);
        }
Пример #12
0
        public async Task ItemEpkQuerySingleKeyRangeValidation()
        {
            IList <ToDoActivity> deleteList = new List <ToDoActivity>();
            CosmosContainer      container  = null;

            try
            {
                // Create a container large enough to have at least 2 partitions
                CosmosContainerResponse containerResponse = await this.database.Containers.CreateContainerAsync(
                    id : Guid.NewGuid().ToString(),
                    partitionKeyPath : "/pk",
                    throughput : 15000);

                container = containerResponse;

                // Get all the partition key ranges to verify there is more than one partition
                IRoutingMapProvider routingMapProvider = await this.cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync();

                IReadOnlyList <PartitionKeyRange> ranges = await routingMapProvider.TryGetOverlappingRangesAsync(
                    containerResponse.Resource.ResourceId,
                    new Documents.Routing.Range <string>("00", "FF", isMaxInclusive: true, isMinInclusive: true));

                // If this fails the RUs of the container needs to be increased to ensure at least 2 partitions.
                Assert.IsTrue(ranges.Count > 1, " RUs of the container needs to be increased to ensure at least 2 partitions.");

                FeedOptions options = new FeedOptions()
                {
                    Properties = new Dictionary <string, object>()
                    {
                        { "x-ms-effective-partition-key-string", "AA" }
                    }
                };

                // Create a bad expression. It will not be called. Expression is not allowed to be null.
                IQueryable <int> queryable  = new List <int>().AsQueryable();
                Expression       expression = queryable.Expression;

                DocumentQueryExecutionContextBase.InitParams inputParams = new DocumentQueryExecutionContextBase.InitParams(
                    new DocumentQueryClient(this.cosmosClient.DocumentClient),
                    ResourceType.Document,
                    typeof(object),
                    expression,
                    options,
                    ((CosmosContainerCore)container).LinkUri.OriginalString,
                    false,
                    Guid.NewGuid());

                DefaultDocumentQueryExecutionContext defaultDocumentQueryExecutionContext = new DefaultDocumentQueryExecutionContext(inputParams, true);

                // There should only be one range since the EPK option is set.
                List <PartitionKeyRange> partitionKeyRanges = await DocumentQueryExecutionContextFactory.GetTargetPartitionKeyRanges(
                    queryExecutionContext : defaultDocumentQueryExecutionContext,
                    partitionedQueryExecutionInfo : null,
                    collection : containerResponse,
                    feedOptions : options);

                Assert.IsTrue(partitionKeyRanges.Count == 1, "Only 1 partition key range should be selected since the EPK option is set.");
            }
            finally
            {
                if (container != null)
                {
                    await container.DeleteAsync();
                }
            }
        }