private static string AddPartitionKeyRangeToContinuationToken(string continuationToken, PartitionKeyRange partitionKeyRange) { return(JsonConvert.SerializeObject(new CompositeContinuationToken { Token = continuationToken, Range = partitionKeyRange.ToRange(), })); }
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()); }
public override async Task <DocumentServiceLease> AcquireAsync(DocumentServiceLease lease) { if (lease == null) { throw new ArgumentNullException(nameof(lease)); } string oldOwner = lease.Owner; // We need to add the range information to any older leases // This would not happen with new created leases but we need to be back compat if (lease.FeedRange == null) { if (!this.lazyContainerRid.ValueInitialized) { TryCatch <string> tryInitializeContainerRId = await this.lazyContainerRid.GetValueAsync(NoOpTrace.Singleton, default); if (!tryInitializeContainerRId.Succeeded) { throw tryInitializeContainerRId.Exception.InnerException; } this.partitionKeyRangeCache = await this.monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); } PartitionKeyRange partitionKeyRange = await this.partitionKeyRangeCache.TryGetPartitionKeyRangeByIdAsync( this.lazyContainerRid.Result.Result, lease.CurrentLeaseToken, NoOpTrace.Singleton); if (partitionKeyRange != null) { lease.FeedRange = new FeedRangeEpk(partitionKeyRange.ToRange()); } } return(await this.leaseUpdater.UpdateLeaseAsync( lease, lease.Id, this.requestOptionsFactory.GetPartitionKey(lease.Id, lease.PartitionKey), serverLease => { if (serverLease.Owner != oldOwner) { DefaultTrace.TraceInformation("{0} lease token was taken over by owner '{1}'", lease.CurrentLeaseToken, serverLease.Owner); throw new LeaseLostException(lease); } serverLease.Owner = this.options.HostName; serverLease.Properties = lease.Properties; return serverLease; }).ConfigureAwait(false)); }
private static PartitionKeyRange MinBefore(IReadOnlyList<PartitionKeyRange> values, PartitionKeyRange minValue) { if (values.Count == 0) { throw new ArgumentException(nameof(values)); } IComparer<Range<string>> comparer = Range<string>.MinComparer.Instance; PartitionKeyRange min = null; foreach (PartitionKeyRange value in values) { if (comparer.Compare(value.ToRange(), minValue.ToRange()) < 0 && (min == null || comparer.Compare(value.ToRange(), min.ToRange()) > 0)) { min = value; } } return min; }
public override Task <DocumentServiceLease> CreateLeaseIfNotExistAsync( PartitionKeyRange partitionKeyRange, string continuationToken) { if (partitionKeyRange == null) { throw new ArgumentNullException(nameof(partitionKeyRange)); } string leaseToken = partitionKeyRange.Id; DocumentServiceLeaseCore documentServiceLease = new DocumentServiceLeaseCore { LeaseId = leaseToken, LeaseToken = leaseToken, ContinuationToken = continuationToken, FeedRange = new FeedRangeEpk(partitionKeyRange.ToRange()) }; return(this.TryCreateDocumentServiceLeaseAsync(documentServiceLease)); }
public override Task <DocumentServiceLease> CreateLeaseIfNotExistAsync( PartitionKeyRange partitionKeyRange, string continuationToken) { if (partitionKeyRange == null) { throw new ArgumentNullException(nameof(partitionKeyRange)); } string leaseToken = partitionKeyRange.Id; string leaseDocId = this.GetDocumentId(leaseToken); DocumentServiceLeaseCore documentServiceLease = new DocumentServiceLeaseCore { LeaseId = leaseDocId, LeaseToken = leaseToken, ContinuationToken = continuationToken, FeedRange = new FeedRangeEpk(partitionKeyRange.ToRange()) }; this.requestOptionsFactory.AddPartitionKeyIfNeeded((string pk) => documentServiceLease.LeasePartitionKey = pk, Guid.NewGuid().ToString()); return(this.TryCreateDocumentServiceLeaseAsync(documentServiceLease)); }
/// <summary> /// Gets the replacement ranges for the target range that got split. /// </summary> /// <param name="targetRange">The target range that got split.</param> /// <param name="collectionRid">The collection rid.</param> /// <returns>The replacement ranges for the target range that got split.</returns> private async Task <List <PartitionKeyRange> > GetReplacementRanges(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 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)); }
private static IList <ToDoItem> GenerateAndMockResponseHelper( Mock <CosmosQueryClient> mockQueryClient, Mock <IRoutingMapProvider> mockRoutingMap, IList <ToDoItem> allItemsOrdered, bool isOrderByQuery, SqlQuerySpec sqlQuerySpec, string containerRid, string initContinuationToken, int maxPageSize, MockPartitionResponse[] mockResponseForSinglePartition, CancellationToken cancellationTokenForMocks) { if (mockResponseForSinglePartition == null) { throw new ArgumentNullException(nameof(mockResponseForSinglePartition)); } // Loop through all the partitions foreach (MockPartitionResponse partitionAndMessages in mockResponseForSinglePartition) { PartitionKeyRange partitionKeyRange = partitionAndMessages.PartitionKeyRange; string previousContinuationToken = initContinuationToken; // Loop through each message inside the partition List <int[]> messages = partitionAndMessages.MessagesWithItemIndex; int messagesCount = messages == null ? 0 : messages.Count; int lastMessageIndex = messagesCount - 1; for (int i = 0; i < messagesCount; i++) { int[] message = partitionAndMessages.MessagesWithItemIndex[i]; string newContinuationToken = null; List <ToDoItem> currentPageItems = new List <ToDoItem>(); // Null represents an empty page if (message != null) { foreach (int itemPosition in message) { currentPageItems.Add(allItemsOrdered[itemPosition]); } } // Last message should have null continuation token // Split means it's not the last message for this PK range if (i != lastMessageIndex || partitionAndMessages.HasSplit) { newContinuationToken = Guid.NewGuid().ToString(); } QueryResponse queryResponse = QueryResponseMessageFactory.CreateQueryResponse( currentPageItems, isOrderByQuery, newContinuationToken, containerRid); mockQueryClient.Setup(x => x.ExecuteItemQueryAsync( It.IsAny <Uri>(), ResourceType.Document, OperationType.Query, containerRid, It.IsAny <QueryRequestOptions>(), It.Is <SqlQuerySpec>(specInput => MockItemProducerFactory.IsSqlQuerySpecEqual(sqlQuerySpec, specInput)), previousContinuationToken, It.Is <PartitionKeyRangeIdentity>(rangeId => string.Equals(rangeId.PartitionKeyRangeId, partitionKeyRange.Id) && string.Equals(rangeId.CollectionRid, containerRid)), It.IsAny <bool>(), maxPageSize, cancellationTokenForMocks)) .Returns(Task.FromResult(queryResponse)); previousContinuationToken = newContinuationToken; } if (partitionAndMessages.HasSplit) { QueryResponse querySplitResponse = QueryResponseMessageFactory.CreateSplitResponse(containerRid); mockRoutingMap.Setup(x => x.TryGetOverlappingRangesAsync( containerRid, It.Is <Documents.Routing.Range <string> >(inputRange => inputRange.Equals(partitionKeyRange.ToRange())), true)).Returns(Task.FromResult(partitionAndMessages.GetPartitionKeyRangeOfSplit())); mockQueryClient.Setup(x => x.ExecuteItemQueryAsync( It.IsAny <Uri>(), ResourceType.Document, OperationType.Query, containerRid, It.IsAny <QueryRequestOptions>(), It.Is <SqlQuerySpec>(specInput => MockItemProducerFactory.IsSqlQuerySpecEqual(sqlQuerySpec, specInput)), previousContinuationToken, It.Is <PartitionKeyRangeIdentity>(rangeId => string.Equals(rangeId.PartitionKeyRangeId, partitionKeyRange.Id) && string.Equals(rangeId.CollectionRid, containerRid)), It.IsAny <bool>(), maxPageSize, cancellationTokenForMocks)) .Returns(Task.FromResult(querySplitResponse)); GenerateAndMockResponseHelper( mockQueryClient: mockQueryClient, mockRoutingMap: mockRoutingMap, allItemsOrdered: allItemsOrdered, isOrderByQuery: isOrderByQuery, sqlQuerySpec: sqlQuerySpec, containerRid: containerRid, initContinuationToken: previousContinuationToken, maxPageSize: maxPageSize, mockResponseForSinglePartition: partitionAndMessages.Split, cancellationTokenForMocks: cancellationTokenForMocks); } } return(allItemsOrdered); }