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()); }
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)); }
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()); }
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); }
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); }
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(); } } }