/// <summary> /// TryAddPartitionKeyRangeToContinuationTokenAsync /// </summary> /// <returns><c>false</c> if collectionRid is likely wrong because range was not found. Cache needs to be refreshed probably.</returns> public virtual async Task <bool> TryAddPartitionKeyRangeToContinuationTokenAsync( INameValueCollection backendResponseHeaders, IReadOnlyList <Range <string> > providedPartitionKeyRanges, IRoutingMapProvider routingMapProvider, string collectionRid, ResolvedRangeInfo resolvedRangeInfo, ITrace trace, 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(), trace)).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, trace); if (nextRange == null) { return(false); } rangeToUse = nextRange; } } if (rangeToUse != null) { backendResponseHeaders[HttpConstants.HttpHeaders.Continuation] = PartitionRoutingHelper.AddPartitionKeyRangeToContinuationToken( backendResponseHeaders[HttpConstants.HttpHeaders.Continuation], rangeToUse); } } return(true); }
public async Task AddPartitionKeyRangeToContinuationTokenOnBoundry() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( "A", "D", isMinInclusive: true, isMaxInclusive: false) }; //Reverse ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, null); IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max), It.IsAny <ITrace>(), It.Is <bool>(x => x == false) )).Returns(Task.FromResult(overlappingRanges)).Verifiable(); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); StoreRequestNameValueCollection headers = new StoreRequestNameValueCollection(); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse ); Assert.IsTrue(result); routingMapProvider.Verify(); string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.First().ToRange(), }); Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation)); //Forward currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "D" }, null); overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "D" }, }.AsReadOnly(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <ITrace>(), It.IsAny <bool>() )).Returns(Task.FromResult(overlappingRanges)); headers = new StoreRequestNameValueCollection(); result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, NoOpTrace.Singleton, RntdbEnumerationDirection.Forward ); Assert.IsTrue(result); routingMapProvider.Verify(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(e => e.IsMaxInclusive), It.IsAny <ITrace>(), It.IsAny <bool>() ), Times.Never); expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.Last().ToRange(), }); Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation)); }
public async Task GetTargetRangeFromContinuationTokenOnSplit() { const string Token = "token"; List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, isMinInclusive: true, isMaxInclusive: false) }; Range <string> rangeFromContinuationToken = new Range <string>("A", "C", true, false); List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken> { new CompositeContinuationToken { Token = Token, Range = rangeFromContinuationToken } }; IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "2", MinInclusive = "A", MaxExclusive = "B" }, new PartitionKeyRange { Id = "3", MinInclusive = "B", MaxExclusive = "C" }, new PartitionKeyRange { Id = "1", MinInclusive = "C", MaxExclusive = "D" } }.AsReadOnly(); IReadOnlyList <PartitionKeyRange> replacedRanges = overlappingRanges.Take(2).ToList().AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min), It.IsAny <ITrace>(), It.Is <bool>(x => x == false) )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min && x.Max == rangeFromContinuationToken.Max), It.IsAny <ITrace>(), It.Is <bool>(x => x == true) )).Returns(Task.FromResult(replacedRanges)).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); ResolvedRangeInfo resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, rangeFromContinuationToken, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); routingMapProvider.Verify(); Assert.IsTrue(replacedRanges.Last().Equals(resolvedRangeInfo.ResolvedRange)); List <PartitionKeyRange> reversedReplacedRanges = new List <PartitionKeyRange>(replacedRanges); reversedReplacedRanges.Reverse(); Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count); Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token); for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++) { Assert.IsTrue(reversedReplacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range)); } //Forward partitionRoutingHelper = new PartitionRoutingHelper(); resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, rangeFromContinuationToken, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Forward); routingMapProvider.Verify(); Assert.IsTrue(replacedRanges.First().Equals(resolvedRangeInfo.ResolvedRange)); Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count); Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token); for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++) { Assert.IsTrue(replacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range)); } }
public async Task GetTargetRangeFromContinuationTokenWhenEmpty() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, isMinInclusive: true, isMaxInclusive: false) }; //Empty Range <string> range = new Range <string>("", "", true, false); List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken> { new CompositeContinuationToken { Range = range } }; IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" } }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <ITrace>(), It.Is <bool>(x => x == false) )).Returns(Task.FromResult(overlappingRanges)).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); ResolvedRangeInfo resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, range, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); Assert.AreEqual(overlappingRanges.Last().Id, resolvedRangeInfo.ResolvedRange.Id); CollectionAssert.AreEqual(suppliedTokens, resolvedRangeInfo.ContinuationTokens); routingMapProvider.Verify(); //Forward routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == range.Min), It.IsAny <ITrace>(), It.IsAny <bool>() )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable(); resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, range, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Forward); Assert.AreEqual(overlappingRanges.First().Id, resolvedRangeInfo.ResolvedRange.Id); CollectionAssert.AreEqual(suppliedTokens, resolvedRangeInfo.ContinuationTokens); routingMapProvider.Verify(); }
public override async Task <ResponseMessage> SendAsync( RequestMessage request, CancellationToken cancellationToken) { ResponseMessage response = null; string originalContinuation = request.Headers.Continuation; try { RntdbEnumerationDirection rntdbEnumerationDirection = RntdbEnumerationDirection.Forward; object direction; if (request.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out 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); object startEpk; object endEpk; if (!request.Properties.TryGetValue(HandlerConstants.StartEpkString, out startEpk)) { startEpk = PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey; } if (!request.Properties.TryGetValue(HandlerConstants.EndEpkString, out endEpk)) { endEpk = PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey; } startEpk = startEpk ?? PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey; endEpk = 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(); ContainerProperties collectionFromCache = await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None); List <CompositeContinuationToken> suppliedTokens; //direction is not expected to change between continuations. Range <string> rangeFromContinuationToken = this.partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(serviceRequest.Headers, out 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 (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; } }
public async Task AddPartitionKeyRangeToContinuationTokenOnNullBackendContinuation() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( "A", "D", isMinInclusive: true, isMaxInclusive: false) }; ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" }, null); IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, currentPartitionKeyRange.ResolvedRange, new PartitionKeyRange { Id = "3", MinInclusive = "C", MaxExclusive = "D" } }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max), It.Is <bool>(x => x == false) )).Returns(Task.FromResult(overlappingRanges)).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); DictionaryNameValueCollection headers = new DictionaryNameValueCollection(); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, RntdbEnumerationDirection.Reverse ); Assert.IsTrue(result); routingMapProvider.Verify(); string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.First().ToRange(), }); Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); //Forward routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <bool>() )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Skip(2).ToList())).Verifiable(); headers = new DictionaryNameValueCollection(); result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, RntdbEnumerationDirection.Forward ); Assert.IsTrue(result); routingMapProvider.Verify(); expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.Last().ToRange(), }); Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); }