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;
        }
Exemple #5
0
        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));
        }
Exemple #10
0
        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);
        }