public async Task FeedRangePKRangeId_GetEffectiveRangesAsync() { Documents.PartitionKeyRange partitionKeyRange = new Documents.PartitionKeyRange() { Id = Guid.NewGuid().ToString(), MinInclusive = "AA", MaxExclusive = "BB" }; FeedRangePartitionKeyRange feedRangePartitionKeyRange = new FeedRangePartitionKeyRange(partitionKeyRange.Id); Routing.IRoutingMapProvider routingProvider = Mock.Of <Routing.IRoutingMapProvider>(); Mock.Get(routingProvider) .Setup(f => f.TryGetPartitionKeyRangeByIdAsync(It.IsAny <string>(), It.Is <string>(s => s == partitionKeyRange.Id), It.IsAny <bool>())) .ReturnsAsync(partitionKeyRange); List <Documents.Routing.Range <string> > ranges = await feedRangePartitionKeyRange.GetEffectiveRangesAsync(routingProvider, null, null); Assert.AreEqual(1, ranges.Count); Assert.AreEqual(partitionKeyRange.ToRange().Min, ranges[0].Min); Assert.AreEqual(partitionKeyRange.ToRange().Max, ranges[0].Max); Mock.Get(routingProvider) .Verify(f => f.TryGetPartitionKeyRangeByIdAsync(It.IsAny <string>(), It.Is <string>(s => s == partitionKeyRange.Id), It.IsAny <bool>()), Times.Once); }
private void HandleSplit(IReadOnlyList <Documents.PartitionKeyRange> keyRanges) { if (keyRanges == null) { throw new ArgumentNullException(nameof(keyRanges)); } // Update current Documents.PartitionKeyRange firstRange = keyRanges[0]; this.currentToken.Range = new Documents.Routing.Range <string>(firstRange.MinInclusive, firstRange.MaxExclusive, true, false); // Add children foreach (Documents.PartitionKeyRange keyRange in keyRanges.Skip(1)) { this.compositeContinuationTokens.Enqueue(new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>(keyRange.MinInclusive, keyRange.MaxExclusive, true, false), Token = this.currentToken.Token }); } }
internal override async Task <List <Documents.Routing.Range <string> > > GetEffectiveRangesAsync( IRoutingMapProvider routingMapProvider, string containerRid, Documents.PartitionKeyDefinition partitionKeyDefinition, ITrace trace) { Documents.PartitionKeyRange pkRange = await routingMapProvider.TryGetPartitionKeyRangeByIdAsync( collectionResourceId : containerRid, partitionKeyRangeId : this.PartitionKeyRangeId, trace : trace, forceRefresh : false); if (pkRange == null) { // Try with a refresh pkRange = await routingMapProvider.TryGetPartitionKeyRangeByIdAsync( collectionResourceId : containerRid, partitionKeyRangeId : this.PartitionKeyRangeId, trace : trace, forceRefresh : true); } if (pkRange == null) { throw CosmosExceptionFactory.Create( statusCode: HttpStatusCode.Gone, message: $"The PartitionKeyRangeId: \"{this.PartitionKeyRangeId}\" is not valid for the current container {containerRid} .", stackTrace: string.Empty, headers: new Headers() { SubStatusCode = SubStatusCodes.PartitionKeyRangeGone, }, error: null, innerException: null, trace: NoOpTrace.Singleton); } return(new List <Documents.Routing.Range <string> > { pkRange.ToRange() }); }
private void CreateChildRanges(IReadOnlyList <Documents.PartitionKeyRange> keyRanges) { if (keyRanges == null) { throw new ArgumentNullException(nameof(keyRanges)); } // Update current Documents.PartitionKeyRange firstRange = keyRanges[0]; this.CurrentToken.Range = new Documents.Routing.Range <string>(firstRange.MinInclusive, firstRange.MaxExclusive, true, false); if (FeedRangeCompositeContinuation.TryParseAsCompositeContinuationToken( this.CurrentToken.Token, out CompositeContinuationToken continuationAsComposite)) { // Update the internal composite continuation continuationAsComposite.Range = this.CurrentToken.Range; this.CurrentToken.Token = JsonConvert.SerializeObject(continuationAsComposite); // Add children foreach (Documents.PartitionKeyRange keyRange in keyRanges.Skip(1)) { continuationAsComposite.Range = keyRange.ToRange(); this.CompositeContinuationTokens.Enqueue( FeedRangeCompositeContinuation.CreateCompositeContinuationTokenForRange( keyRange.MinInclusive, keyRange.MaxExclusive, JsonConvert.SerializeObject(continuationAsComposite))); } } else { // Add children foreach (Documents.PartitionKeyRange keyRange in keyRanges.Skip(1)) { this.CompositeContinuationTokens.Enqueue( FeedRangeCompositeContinuation.CreateCompositeContinuationTokenForRange( keyRange.MinInclusive, keyRange.MaxExclusive, this.CurrentToken.Token)); } } }
public override async Task <List <Documents.Routing.Range <string> > > GetEffectiveRangesAsync( IRoutingMapProvider routingMapProvider, string containerRid, Documents.PartitionKeyDefinition partitionKeyDefinition) { Documents.PartitionKeyRange pkRange = await routingMapProvider.TryGetPartitionKeyRangeByIdAsync( collectionResourceId : containerRid, partitionKeyRangeId : this.PartitionKeyRangeId, forceRefresh : false); if (pkRange == null) { // Try with a refresh pkRange = await routingMapProvider.TryGetPartitionKeyRangeByIdAsync( collectionResourceId : containerRid, partitionKeyRangeId : this.PartitionKeyRangeId, forceRefresh : true); } if (pkRange == null) { throw CosmosExceptionFactory.Create( statusCode: HttpStatusCode.Gone, subStatusCode: (int)SubStatusCodes.PartitionKeyRangeGone, message: $"The PartitionKeyRangeId: \"{this.PartitionKeyRangeId}\" is not valid for the current container {containerRid} .", stackTrace: string.Empty, activityId: string.Empty, requestCharge: 0, retryAfter: null, headers: null, diagnosticsContext: null, error: null, innerException: null); } return(new List <Documents.Routing.Range <string> > { pkRange.ToRange() }); }
public async Task FeedRangeEPK_GetPartitionKeyRangesAsync() { Documents.Routing.Range <string> range = new Documents.Routing.Range <string>("AA", "BB", true, false); Documents.PartitionKeyRange partitionKeyRange = new Documents.PartitionKeyRange() { Id = Guid.NewGuid().ToString(), MinInclusive = range.Min, MaxExclusive = range.Max }; FeedRangePartitionKeyRange feedRangePartitionKeyRange = new FeedRangePartitionKeyRange(partitionKeyRange.Id); IRoutingMapProvider routingProvider = Mock.Of <IRoutingMapProvider>(); Mock.Get(routingProvider) .Setup(f => f.TryGetOverlappingRangesAsync(It.IsAny <string>(), It.Is <Documents.Routing.Range <string> >(s => s == range), It.IsAny <bool>())) .ReturnsAsync(new List <Documents.PartitionKeyRange>() { partitionKeyRange }); FeedRangeEpk feedRangeEPK = new FeedRangeEpk(range); IEnumerable <string> pkRanges = await feedRangeEPK.GetPartitionKeyRangesAsync(routingProvider, null, null, default(CancellationToken)); Assert.AreEqual(1, pkRanges.Count()); Assert.AreEqual(partitionKeyRange.Id, pkRanges.First()); }
public async Task FeedRange_PKRangeId_Serialization() { string continuationToken = "TBD"; string containerRid = Guid.NewGuid().ToString(); DocumentFeedResponse <Documents.PartitionKeyRange> ranges = await this.Container.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.Container.LinkUri); Documents.PartitionKeyRange oneRange = ranges.First(); FeedRangePartitionKeyRange original = new FeedRangePartitionKeyRange(oneRange.Id); FeedRangeCompositeContinuation feedRangeSimpleContinuation = new FeedRangeCompositeContinuation(containerRid, original, new List <Documents.Routing.Range <string> >() { oneRange.ToRange() }, continuationToken); string serialized = feedRangeSimpleContinuation.ToString(); Assert.IsTrue(FeedRangeContinuation.TryParse(serialized, out FeedRangeContinuation feedRangeContinuation)); FeedRangeCompositeContinuation deserialized = feedRangeContinuation as FeedRangeCompositeContinuation; FeedRangePartitionKeyRange deserializedFeedRange = deserialized.FeedRange as FeedRangePartitionKeyRange; Assert.IsNotNull(deserialized, "Error deserializing to FeedRangePartitionKeyRange"); Assert.AreEqual(original.PartitionKeyRangeId, deserializedFeedRange.PartitionKeyRangeId); Assert.AreEqual(continuationToken, deserialized.GetContinuation()); }
/// <summary> /// Initializes a new instance of the ItemProducer class. /// </summary> /// <param name="queryContext">request context</param> /// <param name="querySpecForInit">query spec for initialization</param> /// <param name="partitionKeyRange">The partition key range.</param> /// <param name="produceAsyncCompleteCallback">The callback to call once you are done fetching.</param> /// <param name="equalityComparer">The comparer to use to determine whether the producer has seen a new document.</param> /// <param name="testFlags">Flags used to help faciliate testing.</param> /// <param name="initialPageSize">The initial page size.</param> /// <param name="initialContinuationToken">The initial continuation token.</param> public ItemProducer( CosmosQueryContext queryContext, SqlQuerySpec querySpecForInit, PartitionKeyRange partitionKeyRange, ProduceAsyncCompleteDelegate produceAsyncCompleteCallback, IEqualityComparer <CosmosElement> equalityComparer, TestInjections testFlags, long initialPageSize = 50, string initialContinuationToken = null) { this.bufferedPages = new AsyncCollection <QueryResponseCore>(); // We use a binary semaphore to get the behavior of a mutex, // since fetching documents from the backend using a continuation token is a critical section. this.fetchSemaphore = new SemaphoreSlim(1, 1); this.queryContext = queryContext; this.querySpecForInit = querySpecForInit; this.PartitionKeyRange = partitionKeyRange ?? throw new ArgumentNullException(nameof(partitionKeyRange)); this.produceAsyncCompleteCallback = produceAsyncCompleteCallback ?? throw new ArgumentNullException(nameof(produceAsyncCompleteCallback)); this.equalityComparer = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer)); this.pageSize = initialPageSize; this.CurrentContinuationToken = initialContinuationToken; this.BackendContinuationToken = initialContinuationToken; this.PreviousContinuationToken = initialContinuationToken; if (!string.IsNullOrEmpty(initialContinuationToken)) { this.hasStartedFetching = true; this.IsActive = true; } this.fetchSchedulingMetrics = new SchedulingStopwatch(); this.fetchSchedulingMetrics.Ready(); this.testFlags = testFlags; this.HasMoreResults = true; }
/// <summary> /// Matches ranges to their corresponding continuation token. /// Note that most ranges don't have a corresponding continuation token, so their value will be set to null. /// Also note that in the event of a split two or more ranges will match to the same continuation token. /// </summary> /// <typeparam name="PartitionedToken">The type of token we are matching with.</typeparam> /// <param name="partitionKeyRanges">The partition key ranges to match.</param> /// <param name="partitionedContinuationTokens">The continuation tokens to match with.</param> /// <returns>A dictionary of ranges matched with their continuation tokens.</returns> public static IReadOnlyDictionary <PartitionKeyRange, PartitionedToken> MatchRangesToContinuationTokens <PartitionedToken>( ReadOnlyMemory <PartitionKeyRange> partitionKeyRanges, IReadOnlyList <PartitionedToken> partitionedContinuationTokens) where PartitionedToken : IPartitionedToken { if (partitionedContinuationTokens == null) { throw new ArgumentNullException(nameof(partitionedContinuationTokens)); } Dictionary <PartitionKeyRange, PartitionedToken> partitionKeyRangeToToken = new Dictionary <PartitionKeyRange, PartitionedToken>(); ReadOnlySpan <PartitionKeyRange> partitionKeyRangeSpan = partitionKeyRanges.Span; for (int i = 0; i < partitionKeyRangeSpan.Length; i++) { PartitionKeyRange partitionKeyRange = partitionKeyRangeSpan[i]; foreach (PartitionedToken partitionedToken in partitionedContinuationTokens) { // See if continuation token includes the range if ((partitionKeyRange.MinInclusive.CompareTo(partitionedToken.PartitionRange.Min) >= 0) && (partitionKeyRange.MaxExclusive.CompareTo(partitionedToken.PartitionRange.Max) <= 0)) { partitionKeyRangeToToken[partitionKeyRange] = partitionedToken; break; } } if (!partitionKeyRangeToToken.ContainsKey(partitionKeyRange)) { // Could not find a matching token so just set it to null partitionKeyRangeToToken[partitionKeyRange] = default; } } return(partitionKeyRangeToToken); }
/// <summary> /// <para> /// If a query encounters split up resuming using continuation, we need to regenerate the continuation tokens. /// Specifically, since after split we will have new set of ranges, we need to remove continuation token for the /// parent partition and introduce continuation token for the child partitions. /// </para> /// <para> /// This function does that. Also in that process, we also check validity of the input continuation tokens. For example, /// even after split the boundary ranges of the child partitions should match with the parent partitions. If the Min and Max /// range of a target partition in the continuation token was Min1 and Max1. Then the Min and Max range info for the two /// corresponding child partitions C1Min, C1Max, C2Min, and C2Max should follow the constrain below: /// PMax = C2Max > C2Min > C1Max > C1Min = PMin. /// </para> /// </summary> /// <param name="partitionKeyRanges">The partition key ranges to extract continuation tokens for.</param> /// <param name="suppliedContinuationTokens">The continuation token that the user supplied.</param> /// <param name="targetRangeToContinuationTokenMap">The output dictionary of partition key range to continuation token.</param> /// <typeparam name="TContinuationToken">The type of continuation token to generate.</typeparam> /// <Remarks> /// The code assumes that merge doesn't happen and /// </Remarks> /// <returns>The index of the partition whose MinInclusive is equal to the suppliedContinuationTokens</returns> protected int FindTargetRangeAndExtractContinuationTokens <TContinuationToken>( List <PartitionKeyRange> partitionKeyRanges, IEnumerable <Tuple <TContinuationToken, Documents.Routing.Range <string> > > suppliedContinuationTokens, out Dictionary <string, TContinuationToken> targetRangeToContinuationTokenMap) { if (partitionKeyRanges == null) { throw new ArgumentNullException(nameof(partitionKeyRanges)); } if (partitionKeyRanges.Count < 1) { throw new ArgumentException(nameof(partitionKeyRanges)); } foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) { if (partitionKeyRange == null) { throw new ArgumentException(nameof(partitionKeyRanges)); } } if (suppliedContinuationTokens == null) { throw new ArgumentNullException(nameof(suppliedContinuationTokens)); } if (suppliedContinuationTokens.Count() < 1) { throw new ArgumentException(nameof(suppliedContinuationTokens)); } if (suppliedContinuationTokens.Count() > partitionKeyRanges.Count) { throw new ArgumentException($"{nameof(suppliedContinuationTokens)} can not have more elements than {nameof(partitionKeyRanges)}."); } targetRangeToContinuationTokenMap = new Dictionary <string, TContinuationToken>(); // Find the minimum index. Tuple <TContinuationToken, Documents.Routing.Range <string> > firstContinuationTokenAndRange = suppliedContinuationTokens .OrderBy((tuple) => tuple.Item2.Min) .First(); TContinuationToken firstContinuationToken = firstContinuationTokenAndRange.Item1; PartitionKeyRange firstContinuationRange = new PartitionKeyRange { MinInclusive = firstContinuationTokenAndRange.Item2.Min, MaxExclusive = firstContinuationTokenAndRange.Item2.Max }; int minIndex = partitionKeyRanges.BinarySearch( firstContinuationRange, Comparer <PartitionKeyRange> .Create((range1, range2) => string.CompareOrdinal(range1.MinInclusive, range2.MinInclusive))); if (minIndex < 0) { throw new ArgumentException($"{RMResources.InvalidContinuationToken} - Could not find continuation token: {firstContinuationToken}"); } foreach (Tuple <TContinuationToken, Documents.Routing.Range <string> > suppledContinuationToken in suppliedContinuationTokens) { // find what ranges make up the supplied continuation token TContinuationToken continuationToken = suppledContinuationToken.Item1; Documents.Routing.Range <string> range = suppledContinuationToken.Item2; IEnumerable <PartitionKeyRange> replacementRanges = partitionKeyRanges .Where((partitionKeyRange) => string.CompareOrdinal(range.Min, partitionKeyRange.MinInclusive) <= 0 && string.CompareOrdinal(range.Max, partitionKeyRange.MaxExclusive) >= 0) .OrderBy((partitionKeyRange) => partitionKeyRange.MinInclusive); // Could not find the child ranges if (replacementRanges.Count() == 0) { throw this.queryClient.CreateBadRequestException( $"{RMResources.InvalidContinuationToken} - Could not find continuation token: {continuationToken}"); } // PMax = C2Max > C2Min > C1Max > C1Min = PMin. string parentMax = range.Max; string child2Max = replacementRanges.Last().MaxExclusive; string child2Min = replacementRanges.Last().MinInclusive; string child1Max = replacementRanges.First().MaxExclusive; string child1Min = replacementRanges.First().MinInclusive; string parentMin = range.Min; if (!(parentMax == child2Max && string.CompareOrdinal(child2Max, child2Min) >= 0 && (replacementRanges.Count() == 1 ? true : string.CompareOrdinal(child2Min, child1Max) >= 0) && string.CompareOrdinal(child1Max, child1Min) >= 0 && child1Min == parentMin)) { throw this.queryClient.CreateBadRequestException( $"{RMResources.InvalidContinuationToken} - PMax = C2Max > C2Min > C1Max > C1Min = PMin: {continuationToken}"); } foreach (PartitionKeyRange partitionKeyRange in replacementRanges) { targetRangeToContinuationTokenMap.Add(partitionKeyRange.Id, continuationToken); } } return(minIndex); }
public static TryCatch <PartitionMapping <PartitionedToken> > TryGetInitializationInfo <PartitionedToken>( IReadOnlyList <PartitionKeyRange> partitionKeyRanges, IReadOnlyList <PartitionedToken> partitionedContinuationTokens) where PartitionedToken : IPartitionedToken { if (partitionKeyRanges == null) { throw new ArgumentNullException(nameof(partitionKeyRanges)); } if (partitionedContinuationTokens == null) { throw new ArgumentNullException(nameof(partitionedContinuationTokens)); } if (partitionKeyRanges.Count < 1) { throw new ArgumentException(nameof(partitionKeyRanges)); } if (partitionedContinuationTokens.Count < 1) { throw new ArgumentException(nameof(partitionKeyRanges)); } if (partitionedContinuationTokens.Count > partitionKeyRanges.Count) { throw new ArgumentException($"{nameof(partitionedContinuationTokens)} can not have more elements than {nameof(partitionKeyRanges)}."); } // Find the continuation token for the partition we left off on: PartitionedToken firstContinuationToken = partitionedContinuationTokens .OrderBy((partitionedToken) => partitionedToken.PartitionRange.Min) .First(); // Segment the ranges based off that: ReadOnlyMemory <PartitionKeyRange> sortedRanges = partitionKeyRanges .OrderBy((partitionKeyRange) => partitionKeyRange.MinInclusive) .ToArray(); PartitionKeyRange firstContinuationRange = new PartitionKeyRange { MinInclusive = firstContinuationToken.PartitionRange.Min, MaxExclusive = firstContinuationToken.PartitionRange.Max }; int matchedIndex = sortedRanges.Span.BinarySearch( firstContinuationRange, Comparer <PartitionKeyRange> .Create((range1, range2) => string.CompareOrdinal(range1.MinInclusive, range2.MinInclusive))); if (matchedIndex < 0) { return(TryCatch <PartitionMapping <PartitionedToken> > .FromException( new MalformedContinuationTokenException( $"{RMResources.InvalidContinuationToken} - Could not find continuation token: {firstContinuationToken}"))); } ReadOnlyMemory <PartitionKeyRange> partitionsLeftOfTarget = matchedIndex == 0 ? ReadOnlyMemory <PartitionKeyRange> .Empty : sortedRanges.Slice(start: 0, length: matchedIndex); ReadOnlyMemory <PartitionKeyRange> targetPartition = sortedRanges.Slice(start: matchedIndex, length: 1); ReadOnlyMemory <PartitionKeyRange> partitionsRightOfTarget = matchedIndex == sortedRanges.Length - 1 ? ReadOnlyMemory <PartitionKeyRange> .Empty : sortedRanges.Slice(start: matchedIndex + 1); // Create the continuation token mapping for each region. IReadOnlyDictionary <PartitionKeyRange, PartitionedToken> mappingForPartitionsLeftOfTarget = MatchRangesToContinuationTokens( partitionsLeftOfTarget, partitionedContinuationTokens); IReadOnlyDictionary <PartitionKeyRange, PartitionedToken> mappingForTargetPartition = MatchRangesToContinuationTokens( targetPartition, partitionedContinuationTokens); IReadOnlyDictionary <PartitionKeyRange, PartitionedToken> mappingForPartitionsRightOfTarget = MatchRangesToContinuationTokens( partitionsRightOfTarget, partitionedContinuationTokens); return(TryCatch <PartitionMapping <PartitionedToken> > .FromResult( new PartitionMapping <PartitionedToken>( partitionsLeftOfTarget : mappingForPartitionsLeftOfTarget, targetPartition : mappingForTargetPartition, partitionsRightOfTarget : mappingForPartitionsRightOfTarget))); }
protected async Task <TryCatch> TryInitializeAsync( string collectionRid, int initialPageSize, SqlQuerySpec querySpecForInit, IReadOnlyDictionary <PartitionKeyRange, string> targetRangeToContinuationMap, bool deferFirstPage, string filter, Func <ItemProducerTree, Task <TryCatch> > tryFilterAsync, CancellationToken cancellationToken) { if (collectionRid == null) { throw new ArgumentNullException(nameof(collectionRid)); } if (initialPageSize < 0) { throw new ArgumentOutOfRangeException(nameof(initialPageSize)); } if (querySpecForInit == null) { throw new ArgumentNullException(nameof(querySpecForInit)); } if (targetRangeToContinuationMap == null) { throw new ArgumentNullException(nameof(targetRangeToContinuationMap)); } cancellationToken.ThrowIfCancellationRequested(); List <ItemProducerTree> itemProducerTrees = new List <ItemProducerTree>(); foreach (KeyValuePair <PartitionKeyRange, string> rangeAndContinuationToken in targetRangeToContinuationMap) { PartitionKeyRange partitionKeyRange = rangeAndContinuationToken.Key; string continuationToken = rangeAndContinuationToken.Value; ItemProducerTree itemProducerTree = new ItemProducerTree( this.queryContext, querySpecForInit, partitionKeyRange, this.OnItemProducerTreeCompleteFetching, this.itemProducerForest.Comparer, this.equalityComparer, this.testSettings, deferFirstPage, collectionRid, initialPageSize, continuationToken) { Filter = filter }; // Prefetch if necessary, and populate consume queue. if (this.CanPrefetch) { this.TryScheduleFetch(itemProducerTree); } itemProducerTrees.Add(itemProducerTree); } // Using loop fission so that we can load the document producers in parallel foreach (ItemProducerTree itemProducerTree in itemProducerTrees) { if (!deferFirstPage) { while (true) { (bool movedToNextPage, QueryResponseCore? failureResponse) = await itemProducerTree.TryMoveNextPageAsync(cancellationToken); if (failureResponse.HasValue) { return(TryCatch.FromException( failureResponse.Value.CosmosException)); } if (!movedToNextPage) { break; } if (itemProducerTree.IsAtBeginningOfPage) { break; } if (itemProducerTree.TryMoveNextDocumentWithinPage()) { break; } } } if (tryFilterAsync != null) { TryCatch tryFilter = await tryFilterAsync(itemProducerTree); if (!tryFilter.Succeeded) { return(tryFilter); } } this.itemProducerForest.Enqueue(itemProducerTree); } return(TryCatch.FromResult()); }
/// <summary> /// Initializes a new instance of the ItemProducerTree class. /// </summary> /// <param name="queryContext">query context.</param> /// <param name="querySpecForInit">query spec init.</param> /// <param name="partitionKeyRange">The partition key range.</param> /// <param name="produceAsyncCompleteCallback">Callback to invoke once a fetch finishes.</param> /// <param name="itemProducerTreeComparer">Comparer to determine, which tree to produce from.</param> /// <param name="equalityComparer">Comparer to see if we need to return the continuation token for a partition.</param> /// <param name="testSettings">Test flags.</param> /// <param name="deferFirstPage">Whether or not to defer fetching the first page.</param> /// <param name="collectionRid">The collection to drain from.</param> /// <param name="initialPageSize">The initial page size.</param> /// <param name="initialContinuationToken">The initial continuation token.</param> public ItemProducerTree( CosmosQueryContext queryContext, SqlQuerySpec querySpecForInit, Documents.PartitionKeyRange partitionKeyRange, ProduceAsyncCompleteDelegate produceAsyncCompleteCallback, IComparer <ItemProducerTree> itemProducerTreeComparer, IEqualityComparer <CosmosElement> equalityComparer, TestInjections testSettings, bool deferFirstPage, string collectionRid, long initialPageSize = 50, string initialContinuationToken = null) { if (queryContext == null) { throw new ArgumentNullException($"{nameof(queryContext)}"); } if (itemProducerTreeComparer == null) { throw new ArgumentNullException($"{nameof(itemProducerTreeComparer)}"); } if (produceAsyncCompleteCallback == null) { throw new ArgumentNullException($"{nameof(produceAsyncCompleteCallback)}"); } if (itemProducerTreeComparer == null) { throw new ArgumentNullException($"{nameof(itemProducerTreeComparer)}"); } if (equalityComparer == null) { throw new ArgumentNullException($"{nameof(equalityComparer)}"); } if (string.IsNullOrEmpty(collectionRid)) { throw new ArgumentException($"{nameof(collectionRid)} can not be null or empty."); } this.Root = new ItemProducer( queryContext, querySpecForInit, partitionKeyRange, (itemsBuffered, resourceUnitUsage, diagnostics, requestLength, token) => produceAsyncCompleteCallback(this, itemsBuffered, resourceUnitUsage, diagnostics, requestLength, token), equalityComparer, testSettings, initialPageSize, initialContinuationToken); this.queryClient = queryContext.QueryClient; this.children = new PriorityQueue <ItemProducerTree>(itemProducerTreeComparer, true); this.deferFirstPage = deferFirstPage; this.collectionRid = collectionRid; this.createItemProducerTreeCallback = ItemProducerTree.CreateItemProducerTreeCallback( queryContext, querySpecForInit, produceAsyncCompleteCallback, itemProducerTreeComparer, equalityComparer, testSettings, deferFirstPage, collectionRid, initialPageSize); this.executeWithSplitProofingSemaphore = new SemaphoreSlim(1, 1); }