/// <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 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); }
public override async Task <ResponseMessage> SendAsync( RequestMessage request, CancellationToken cancellationToken) { using (ITrace childTrace = request.Trace.StartChild(this.FullHandlerName, TraceComponent.RequestHandler, Tracing.TraceLevel.Info)) { request.Trace = childTrace; ResponseMessage response = null; string originalContinuation = request.Headers.ContinuationToken; try { RntdbEnumerationDirection rntdbEnumerationDirection = RntdbEnumerationDirection.Forward; if (request.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object direction)) { rntdbEnumerationDirection = (byte)direction == (byte)RntdbEnumerationDirection.Reverse ? RntdbEnumerationDirection.Reverse : RntdbEnumerationDirection.Forward; } request.Headers.Remove(HttpConstants.HttpHeaders.IsContinuationExpected); request.Headers.Add(HttpConstants.HttpHeaders.IsContinuationExpected, bool.TrueString); if (!request.Properties.TryGetValue(HandlerConstants.StartEpkString, out object startEpk)) { startEpk = PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey; } if (!request.Properties.TryGetValue(HandlerConstants.EndEpkString, out object endEpk)) { endEpk = PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey; } startEpk ??= PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey; endEpk ??= PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey; List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( (string)startEpk, (string)endEpk, isMinInclusive: true, isMaxInclusive: false) }; DocumentServiceRequest serviceRequest = request.ToDocumentServiceRequest(); PartitionKeyRangeCache routingMapProvider = await this.client.DocumentClient.GetPartitionKeyRangeCacheAsync(); CollectionCache collectionCache = await this.client.DocumentClient.GetCollectionCacheAsync(NoOpTrace.Singleton); ContainerProperties collectionFromCache = await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None); //direction is not expected to change between continuations. Range <string> rangeFromContinuationToken = this.partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(serviceRequest.Headers, out List <CompositeContinuationToken> suppliedTokens); ResolvedRangeInfo resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedPartitionKeyRanges : providedRanges, routingMapProvider : routingMapProvider, collectionRid : collectionFromCache.ResourceId, rangeFromContinuationToken : rangeFromContinuationToken, suppliedTokens : suppliedTokens, direction : rntdbEnumerationDirection); if (serviceRequest.IsNameBased && resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null) { serviceRequest.ForceNameCacheRefresh = true; collectionFromCache = await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None); resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedPartitionKeyRanges : providedRanges, routingMapProvider : routingMapProvider, collectionRid : collectionFromCache.ResourceId, rangeFromContinuationToken : rangeFromContinuationToken, suppliedTokens : suppliedTokens, direction : rntdbEnumerationDirection); } if (resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null) { return(((DocumentClientException) new NotFoundException( $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Was not able to get queryRoutingInfo even after resolve collection async with force name cache refresh to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}") ).ToCosmosResponseMessage(request)); } serviceRequest.RouteTo(new PartitionKeyRangeIdentity(collectionFromCache.ResourceId, resolvedRangeInfo.ResolvedRange.Id)); response = await base.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { this.SetOriginalContinuationToken(request, response, originalContinuation); } else { if (!await this.partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( response.Headers.CosmosMessageHeaders, providedPartitionKeyRanges: providedRanges, routingMapProvider: routingMapProvider, collectionRid: collectionFromCache.ResourceId, resolvedRangeInfo: resolvedRangeInfo, direction: rntdbEnumerationDirection)) { return(((DocumentClientException) new NotFoundException( $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Call to TryAddPartitionKeyRangeToContinuationTokenAsync failed to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}") ).ToCosmosResponseMessage(request)); } } return(response); } catch (DocumentClientException ex) { ResponseMessage errorResponse = ex.ToCosmosResponseMessage(request); this.SetOriginalContinuationToken(request, errorResponse, originalContinuation); return(errorResponse); } catch (CosmosException ex) { ResponseMessage errorResponse = ex.ToCosmosResponseMessage(request); this.SetOriginalContinuationToken(request, errorResponse, originalContinuation); return(errorResponse); } catch (AggregateException ex) { this.SetOriginalContinuationToken(request, response, originalContinuation); // TODO: because the SDK underneath this path uses ContinueWith or task.Result we need to catch AggregateExceptions here // in order to ensure that underlying DocumentClientExceptions get propagated up correctly. Once all ContinueWith and .Result // is removed this catch can be safely removed. AggregateException innerExceptions = ex.Flatten(); Exception docClientException = innerExceptions.InnerExceptions.FirstOrDefault(innerEx => innerEx is DocumentClientException); if (docClientException != null) { return(((DocumentClientException)docClientException).ToCosmosResponseMessage(request)); } throw; } } }