protected override async Task <TryCatch <OrderByQueryPage> > GetNextPageAsync(ITrace trace, CancellationToken cancellationToken) { // Unfortunately we need to keep both the epk range and partition key for queries // Since the continuation token format uses epk range even though we only need the partition key to route the request. FeedRangeInternal feedRange = this.PartitionKey.HasValue ? new FeedRangePartitionKey(this.PartitionKey.Value) : this.FeedRangeState.FeedRange; TryCatch <QueryPage> monadicQueryPage = await this.queryDataSource .MonadicQueryAsync( sqlQuerySpec : this.SqlQuerySpec, feedRangeState : new FeedRangeState <QueryState>(feedRange, this.FeedRangeState.State), queryPaginationOptions : this.queryPaginationOptions, trace : trace, cancellationToken); if (monadicQueryPage.Failed) { return(TryCatch <OrderByQueryPage> .FromException(monadicQueryPage.Exception)); } QueryPage queryPage = monadicQueryPage.Result; return(TryCatch <OrderByQueryPage> .FromResult(new OrderByQueryPage(queryPage))); }
public ReadFeedContinuationToken(FeedRangeInternal feedRange, ReadFeedState readFeedState) { this.Range = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); this.State = readFeedState ?? throw new ArgumentNullException(nameof(readFeedState)); }
public async Task ReadFeedIteratorCore_ReadNextAsync() { string continuation = "TBD"; ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK); responseMessage.Headers.ContinuationToken = continuation; responseMessage.Headers[Documents.HttpConstants.HttpHeaders.ItemCount] = "1"; responseMessage.Content = new MemoryStream(Encoding.UTF8.GetBytes("{}")); Mock <CosmosClientContext> cosmosClientContext = new Mock <CosmosClientContext>(); cosmosClientContext.Setup(c => c.ClientOptions).Returns(new CosmosClientOptions()); cosmosClientContext .Setup(c => c.ProcessResourceOperationStreamAsync( It.IsAny <string>(), It.Is <Documents.ResourceType>(rt => rt == Documents.ResourceType.Document), It.IsAny <Documents.OperationType>(), It.IsAny <RequestOptions>(), It.IsAny <ContainerInternal>(), It.IsAny <PartitionKey?>(), It.IsAny <Stream>(), It.IsAny <Action <RequestMessage> >(), It.IsAny <CosmosDiagnosticsContext>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(responseMessage)); ContainerInternal containerCore = Mock.Of <ContainerInternal>(); Mock.Get(containerCore) .Setup(c => c.ClientContext) .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of <FeedRangeInternal>(); Mock.Get(range) .Setup(f => f.Accept(It.IsAny <FeedRangeVisitor>())); FeedRangeContinuation feedToken = Mock.Of <FeedRangeContinuation>(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); Mock.Get(feedToken) .Setup(f => f.Accept(It.IsAny <FeedRangeVisitor>(), It.IsAny <Action <RequestMessage, string> >())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); Mock.Get(feedToken) .Setup(f => f.GetContinuation()) .Returns(continuation); Mock.Get(feedToken) .Setup(f => f.IsDone) .Returns(true); FeedRangeIteratorCore feedTokenIterator = new FeedRangeIteratorCore(containerCore, feedToken, new QueryRequestOptions(), Documents.ResourceType.Document, queryDefinition: null); ResponseMessage response = await feedTokenIterator.ReadNextAsync(); Mock.Get(feedToken) .Verify(f => f.ReplaceContinuation(It.Is <string>(ct => ct == continuation)), Times.Once); Mock.Get(feedToken) .Verify(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>()), Times.Once); }
public override Task <List <PartitionKeyRange> > GetTargetPartitionKeyRangeByFeedRangeAsync(string resourceLink, string collectionResourceId, PartitionKeyDefinition partitionKeyDefinition, FeedRangeInternal feedRangeInternal, bool forceRefresh, ITrace trace) { throw new NotImplementedException(); }
public Task <TryCatch> MonadicSplitAsync( FeedRangeInternal feedRange, CancellationToken cancellationToken) => Task.FromResult(TryCatch.FromException(new NotSupportedException()));
public abstract Task <List <Documents.PartitionKeyRange> > GetTargetPartitionKeyRangeByFeedRangeAsync( string resourceLink, string collectionResourceId, Documents.PartitionKeyDefinition partitionKeyDefinition, FeedRangeInternal feedRangeInternal);
public FeedRangeState(FeedRangeInternal feedRange, TState state) { this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); this.State = state; }
public Task <TryCatch <List <FeedRangeEpk> > > MonadicGetChildRangeAsync( FeedRangeInternal feedRange, CancellationToken cancellationToken) => this.monadicDocumentContainer.MonadicGetChildRangeAsync( feedRange, cancellationToken);
/// <summary> /// Intializes an instance of the <see cref="ChangeFeedStartFromNow"/> class. /// </summary> /// <param name="feedRange">The (optional) feed range to start from.</param> public ChangeFeedStartFromNow(FeedRangeInternal feedRange) : base(feedRange) { }
public static QueryIterator Create( CosmosQueryClient client, CosmosClientContext clientContext, SqlQuerySpec sqlQuerySpec, string continuationToken, FeedRangeInternal feedRangeInternal, QueryRequestOptions queryRequestOptions, Uri resourceLink, bool isContinuationExpected, bool allowNonValueAggregateQuery, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo) { if (queryRequestOptions == null) { queryRequestOptions = new QueryRequestOptions(); } CosmosDiagnosticsContext queryPipelineCreationDiagnostics = CosmosDiagnosticsContext.Create(queryRequestOptions); CosmosQueryContextCore cosmosQueryContext = new CosmosQueryContextCore( client: client, queryRequestOptions: queryRequestOptions, resourceTypeEnum: Documents.ResourceType.Document, operationType: Documents.OperationType.Query, resourceType: typeof(QueryResponseCore), resourceLink: resourceLink, isContinuationExpected: isContinuationExpected, allowNonValueAggregateQuery: allowNonValueAggregateQuery, diagnosticsContext: queryPipelineCreationDiagnostics, correlatedActivityId: Guid.NewGuid()); CosmosElement requestContinuationToken; switch (queryRequestOptions.ExecutionEnvironment.GetValueOrDefault(ExecutionEnvironment.Client)) { case ExecutionEnvironment.Client: if (continuationToken != null) { if (!CosmosElement.TryParse(continuationToken, out requestContinuationToken)) { return(new QueryIterator( cosmosQueryContext, new QueryExecutionContextWithException( new MalformedContinuationTokenException( $"Malformed Continuation Token: {continuationToken}")), queryRequestOptions.CosmosSerializationFormatOptions, queryRequestOptions, clientContext)); } } else { requestContinuationToken = null; } break; case ExecutionEnvironment.Compute: requestContinuationToken = queryRequestOptions.CosmosElementContinuationToken; break; default: throw new ArgumentOutOfRangeException($"Unknown {nameof(ExecutionEnvironment)}: {queryRequestOptions.ExecutionEnvironment.Value}."); } CosmosQueryExecutionContextFactory.InputParameters inputParameters = new CosmosQueryExecutionContextFactory.InputParameters( sqlQuerySpec: sqlQuerySpec, initialUserContinuationToken: requestContinuationToken, initialFeedRange: feedRangeInternal, maxConcurrency: queryRequestOptions.MaxConcurrency, maxItemCount: queryRequestOptions.MaxItemCount, maxBufferedItemCount: queryRequestOptions.MaxBufferedItemCount, partitionKey: queryRequestOptions.PartitionKey, properties: queryRequestOptions.Properties, partitionedQueryExecutionInfo: partitionedQueryExecutionInfo, executionEnvironment: queryRequestOptions.ExecutionEnvironment, returnResultsInDeterministicOrder: queryRequestOptions.ReturnResultsInDeterministicOrder, testInjections: queryRequestOptions.TestSettings); return(new QueryIterator( cosmosQueryContext, CosmosQueryExecutionContextFactory.Create(cosmosQueryContext, inputParameters), queryRequestOptions.CosmosSerializationFormatOptions, queryRequestOptions, clientContext)); }
/// <summary> /// Initializes an instance of the <see cref="ChangeFeedStartFromBeginning"/> class. /// </summary> /// <param name="feedRange">The (optional) range to start from.</param> public ChangeFeedStartFromBeginning(FeedRangeInternal feedRange) : base(feedRange) { }
public async ValueTask <bool> MoveNextAsync(ITrace trace) { if (trace == null) { throw new ArgumentNullException(nameof(trace)); } this.cancellationToken.ThrowIfCancellationRequested(); using (ITrace childTrace = trace.StartChild(name: nameof(MoveNextAsync), component: TraceComponent.Pagination, level: TraceLevel.Info)) { IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync( childTrace, cancellationToken : this.cancellationToken); if (enumerators.Count == 0) { this.Current = default; this.CurrentRange = default; return(false); } PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue(); if (!await currentPaginator.MoveNextAsync(childTrace)) { // Current enumerator is empty, // so recursively retry on the next enumerator. return(await this.MoveNextAsync(childTrace)); } if (currentPaginator.Current.Failed) { // Check if it's a retryable exception. Exception exception = currentPaginator.Current.Exception; while (exception.InnerException != null) { exception = exception.InnerException; } if (IsSplitException(exception)) { // Handle split List <FeedRangeEpk> childRanges = await this.feedRangeProvider.GetChildRangeAsync( currentPaginator.FeedRangeState.FeedRange, childTrace, this.cancellationToken); if (childRanges.Count == 0) { throw new InvalidOperationException("Got back no children"); } if (childRanges.Count == 1) { // We optimistically assumed that the cache is not stale. // In the event that it is (where we only get back one child / the partition that we think got split) // Then we need to refresh the cache await this.feedRangeProvider.RefreshProviderAsync(childTrace, this.cancellationToken); childRanges = await this.feedRangeProvider.GetChildRangeAsync( currentPaginator.FeedRangeState.FeedRange, childTrace, this.cancellationToken); } if (childRanges.Count() <= 1) { throw new InvalidOperationException("Expected more than 1 child"); } foreach (FeedRangeInternal childRange in childRanges) { PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator( new FeedRangeState <TState>(childRange, currentPaginator.FeedRangeState.State)); enumerators.Enqueue(childPaginator); } // Recursively retry return(await this.MoveNextAsync(childTrace)); } // Just enqueue the paginator and the user can decide if they want to retry. enumerators.Enqueue(currentPaginator); this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromException(currentPaginator.Current.Exception); this.CurrentRange = currentPaginator.FeedRangeState.FeedRange; return(true); } if (currentPaginator.FeedRangeState.State != default) { // Don't enqueue the paginator otherwise it's an infinite loop. enumerators.Enqueue(currentPaginator); } CrossFeedRangeState <TState> crossPartitionState; if (enumerators.Count == 0) { crossPartitionState = null; } else { FeedRangeState <TState>[] feedRangeAndStates = new FeedRangeState <TState> [enumerators.Count]; int i = 0; foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators) { feedRangeAndStates[i++] = enumerator.FeedRangeState; } crossPartitionState = new CrossFeedRangeState <TState>(feedRangeAndStates); } this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromResult( new CrossFeedRangePage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState)); this.CurrentRange = currentPaginator.FeedRangeState.FeedRange; return(true); } }
public async ValueTask <bool> MoveNextAsync() { this.cancellationToken.ThrowIfCancellationRequested(); IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync(cancellationToken : this.cancellationToken); if (enumerators.Count == 0) { this.Current = default; this.CurrentRange = default; return(false); } PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue(); if (!await currentPaginator.MoveNextAsync()) { // Current enumerator is empty, // so recursively retry on the next enumerator. return(await this.MoveNextAsync()); } if (currentPaginator.Current.Failed) { // Check if it's a retryable exception. Exception exception = currentPaginator.Current.Exception; while (exception.InnerException != null) { exception = exception.InnerException; } if (IsSplitException(exception)) { // Handle split IEnumerable <FeedRangeInternal> childRanges = await this.feedRangeProvider.GetChildRangeAsync( currentPaginator.Range, cancellationToken : this.cancellationToken); foreach (FeedRangeInternal childRange in childRanges) { PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator( childRange, currentPaginator.State); enumerators.Enqueue(childPaginator); } // Recursively retry return(await this.MoveNextAsync()); } if (IsMergeException(exception)) { throw new NotImplementedException(); } // Just enqueue the paginator and the user can decide if they want to retry. enumerators.Enqueue(currentPaginator); this.Current = TryCatch <CrossPartitionPage <TPage, TState> > .FromException(currentPaginator.Current.Exception); this.CurrentRange = currentPaginator.Range; return(true); } if (currentPaginator.State != default) { // Don't enqueue the paginator otherwise it's an infinite loop. enumerators.Enqueue(currentPaginator); } CrossPartitionState <TState> crossPartitionState; if (enumerators.Count == 0) { crossPartitionState = null; } else { List <(FeedRangeInternal, TState)> feedRangeAndStates = new List <(FeedRangeInternal, TState)>(enumerators.Count); foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators) { feedRangeAndStates.Add((enumerator.Range, enumerator.State)); } crossPartitionState = new CrossPartitionState <TState>(feedRangeAndStates); } this.Current = TryCatch <CrossPartitionPage <TPage, TState> > .FromResult( new CrossPartitionPage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState)); this.CurrentRange = currentPaginator.Range; return(true); }
public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) : base(feedRange) { this.Etag = etag; }
public async ValueTask <bool> MoveNextAsync() { this.cancellationToken.ThrowIfCancellationRequested(); if (this.bufferedException.HasValue) { this.Current = this.bufferedException.Value; this.bufferedException = null; return(true); } if (!await this.crossPartitionEnumerator.MoveNextAsync()) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } TryCatch <CrossPartitionPage <ChangeFeedPage, ChangeFeedState> > monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { this.Current = TryCatch <ChangeFeedPage> .FromException(monadicCrossPartitionPage.Exception); return(true); } CrossPartitionPage <ChangeFeedPage, ChangeFeedState> crossPartitionPage = monadicCrossPartitionPage.Result; ChangeFeedPage backendPage = crossPartitionPage.Page; if (backendPage is ChangeFeedNotModifiedPage) { // Keep draining the cross partition enumerator until // We get a non 304 page or we loop back to the same range or run into an exception FeedRangeInternal originalRange = this.crossPartitionEnumerator.CurrentRange; double totalRequestCharge = backendPage.RequestCharge; do { if (!await this.crossPartitionEnumerator.MoveNextAsync()) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { // Buffer the exception, since we need to return the request charge so far. this.bufferedException = TryCatch <ChangeFeedPage> .FromException(monadicCrossPartitionPage.Exception); } else { crossPartitionPage = monadicCrossPartitionPage.Result; backendPage = crossPartitionPage.Page; totalRequestCharge += backendPage.RequestCharge; } }while (!(backendPage is ChangeFeedSuccessPage || this.crossPartitionEnumerator.CurrentRange.Equals(originalRange) || this.bufferedException.HasValue)); // Create a page with the aggregated request charge if (backendPage is ChangeFeedSuccessPage changeFeedSuccessPage) { backendPage = new ChangeFeedSuccessPage( changeFeedSuccessPage.Content, totalRequestCharge, changeFeedSuccessPage.ActivityId, changeFeedSuccessPage.State); } else { backendPage = new ChangeFeedNotModifiedPage( totalRequestCharge, backendPage.ActivityId, backendPage.State); } } CrossPartitionState <ChangeFeedState> crossPartitionState = crossPartitionPage.State; List <CosmosElement> changeFeedContinuationTokens = new List <CosmosElement>(); foreach ((FeedRangeInternal range, ChangeFeedState state)rangeAndState in crossPartitionState.Value) { ChangeFeedContinuationToken changeFeedContinuationToken = new ChangeFeedContinuationToken( rangeAndState.range, rangeAndState.state); CosmosElement cosmosElementChangeFeedContinuationToken = ChangeFeedContinuationToken.ToCosmosElement(changeFeedContinuationToken); changeFeedContinuationTokens.Add(cosmosElementChangeFeedContinuationToken); } CosmosArray cosmosElementTokens = CosmosArray.Create(changeFeedContinuationTokens); ChangeFeedState state = ChangeFeedState.Continuation(cosmosElementTokens); ChangeFeedPage compositePage; if (backendPage is ChangeFeedSuccessPage successPage) { compositePage = new ChangeFeedSuccessPage( successPage.Content, successPage.RequestCharge, successPage.ActivityId, state); } else { compositePage = new ChangeFeedNotModifiedPage( backendPage.RequestCharge, backendPage.ActivityId, state); } this.Current = TryCatch <ChangeFeedPage> .FromResult(compositePage); return(true); }
public async ValueTask <bool> MoveNextAsync(ITrace trace) { this.cancellationToken.ThrowIfCancellationRequested(); if (trace == null) { throw new ArgumentNullException(nameof(trace)); } using (ITrace changeFeedMoveNextTrace = trace.StartChild("ChangeFeed MoveNextAsync", TraceComponent.ChangeFeed, TraceLevel.Info)) { if (this.bufferedException.HasValue) { this.Current = this.bufferedException.Value; this.bufferedException = null; return(true); } if (!await this.crossPartitionEnumerator.MoveNextAsync(changeFeedMoveNextTrace)) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } TryCatch <CrossFeedRangePage <ChangeFeedPage, ChangeFeedState> > monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { this.Current = TryCatch <CrossFeedRangePage <ChangeFeedPage, ChangeFeedState> > .FromException(monadicCrossPartitionPage.Exception); return(true); } CrossFeedRangePage <ChangeFeedPage, ChangeFeedState> crossFeedRangePage = monadicCrossPartitionPage.Result; ChangeFeedPage backendPage = crossFeedRangePage.Page; if (backendPage is ChangeFeedNotModifiedPage) { // Keep draining the cross partition enumerator until // We get a non 304 page or we loop back to the same range or run into an exception FeedRangeInternal originalRange = this.crossPartitionEnumerator.CurrentRange; // No point on draining when the state has 1 range if (!IsNextRangeEqualToOriginal(this.crossPartitionEnumerator, originalRange)) { using (ITrace drainNotModifedPages = changeFeedMoveNextTrace.StartChild("Drain NotModified Pages", TraceComponent.ChangeFeed, TraceLevel.Info)) { double totalRequestCharge = backendPage.RequestCharge; do { if (!await this.crossPartitionEnumerator.MoveNextAsync(drainNotModifedPages)) { throw new InvalidOperationException("ChangeFeed should always have a next page."); } monadicCrossPartitionPage = this.crossPartitionEnumerator.Current; if (monadicCrossPartitionPage.Failed) { // Buffer the exception, since we need to return the request charge so far. this.bufferedException = TryCatch <CrossFeedRangePage <ChangeFeedPage, ChangeFeedState> > .FromException(monadicCrossPartitionPage.Exception); } else { crossFeedRangePage = monadicCrossPartitionPage.Result; backendPage = crossFeedRangePage.Page; totalRequestCharge += backendPage.RequestCharge; } }while (!(backendPage is ChangeFeedSuccessPage || IsNextRangeEqualToOriginal(this.crossPartitionEnumerator, originalRange) || this.bufferedException.HasValue)); // Create a page with the aggregated request charge if (backendPage is ChangeFeedSuccessPage changeFeedSuccessPage) { backendPage = new ChangeFeedSuccessPage( changeFeedSuccessPage.Content, totalRequestCharge, changeFeedSuccessPage.ActivityId, changeFeedSuccessPage.AdditionalHeaders, changeFeedSuccessPage.State); } else { backendPage = new ChangeFeedNotModifiedPage( totalRequestCharge, backendPage.ActivityId, backendPage.AdditionalHeaders, backendPage.State); } } } } crossFeedRangePage = new CrossFeedRangePage <ChangeFeedPage, ChangeFeedState>( backendPage, crossFeedRangePage.State); this.Current = TryCatch <CrossFeedRangePage <ChangeFeedPage, ChangeFeedState> > .FromResult(crossFeedRangePage); return(true); } }
public async Task ChangeFeedIteratorCore_OfT_ReadNextAsync() { string continuation = "TBD"; ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK); responseMessage.Headers.ETag = continuation; responseMessage.Headers[Documents.HttpConstants.HttpHeaders.ItemCount] = "1"; Mock <CosmosClientContext> cosmosClientContext = new Mock <CosmosClientContext>(); cosmosClientContext.Setup(c => c.ClientOptions).Returns(new CosmosClientOptions()); cosmosClientContext .Setup(c => c.ProcessResourceOperationStreamAsync( It.IsAny <string>(), It.IsAny <Documents.ResourceType>(), It.IsAny <Documents.OperationType>(), It.IsAny <RequestOptions>(), It.IsAny <ContainerInternal>(), It.IsAny <PartitionKey?>(), It.IsAny <Stream>(), It.IsAny <Action <RequestMessage> >(), It.IsAny <CosmosDiagnosticsContext>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(responseMessage)); ContainerInternal containerCore = Mock.Of <ContainerInternal>(); Mock.Get(containerCore) .Setup(c => c.ClientContext) .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of <FeedRangeInternal>(); Mock.Get(range) .Setup(f => f.Accept(It.IsAny <FeedRangeRequestMessagePopulatorVisitor>())); FeedRangeContinuation feedToken = Mock.Of <FeedRangeContinuation>(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); Mock.Get(feedToken) .Setup(f => f.HandleChangeFeedNotModified(It.IsAny <ResponseMessage>())) .Returns(Documents.ShouldRetryResult.NoRetry()); ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); bool creatorCalled = false; Func <ResponseMessage, FeedResponse <dynamic> > creator = (ResponseMessage r) => { creatorCalled = true; return(Mock.Of <FeedResponse <dynamic> >()); }; FeedIteratorCore <dynamic> changeFeedIteratorCoreOfT = new FeedIteratorCore <dynamic>(changeFeedIteratorCore, creator); FeedResponse <dynamic> response = await changeFeedIteratorCoreOfT.ReadNextAsync(); Mock.Get(feedToken) .Verify(f => f.ReplaceContinuation(It.Is <string>(ct => ct == continuation)), Times.Once); Mock.Get(feedToken) .Verify(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>()), Times.Once); Mock.Get(feedToken) .Verify(f => f.HandleChangeFeedNotModified(It.IsAny <ResponseMessage>()), Times.Once); Assert.IsTrue(creatorCalled, "Response creator not called"); }
public async ValueTask <bool> MoveNextAsync(ITrace trace) { if (trace == null) { throw new ArgumentNullException(nameof(trace)); } using (ITrace childTrace = trace.StartChild(name: nameof(MoveNextAsync), component: TraceComponent.Pagination, level: TraceLevel.Info)) { IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync( childTrace, cancellationToken : this.cancellationToken); if (enumerators.Count == 0) { this.Current = default; this.CurrentRange = default; this.nextState = default; return(false); } PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue(); currentPaginator.SetCancellationToken(this.cancellationToken); bool moveNextResult = false; try { moveNextResult = await currentPaginator.MoveNextAsync(childTrace); } catch { // Re-queue the enumerator to avoid emptying the queue enumerators.Enqueue(currentPaginator); throw; } if (!moveNextResult) { // Current enumerator is empty, // so recursively retry on the next enumerator. return(await this.MoveNextAsync(childTrace)); } if (currentPaginator.Current.Failed) { // Check if it's a retryable exception. Exception exception = currentPaginator.Current.Exception; while (exception.InnerException != null) { exception = exception.InnerException; } if (IsSplitException(exception)) { // Handle split List <FeedRangeEpk> childRanges = await this.feedRangeProvider.GetChildRangeAsync( currentPaginator.FeedRangeState.FeedRange, childTrace, this.cancellationToken); if (childRanges.Count <= 1) { // We optimistically assumed that the cache is not stale. // In the event that it is (where we only get back one child / the partition that we think got split) // Then we need to refresh the cache await this.feedRangeProvider.RefreshProviderAsync(childTrace, this.cancellationToken); childRanges = await this.feedRangeProvider.GetChildRangeAsync( currentPaginator.FeedRangeState.FeedRange, childTrace, this.cancellationToken); } if (childRanges.Count < 1) { string errorMessage = "SDK invariant violated 4795CC37: Must have at least one EPK range in a cross partition enumerator"; throw Resource.CosmosExceptions.CosmosExceptionFactory.CreateInternalServerErrorException( message: errorMessage, headers: null, stackTrace: null, trace: childTrace, error: new Microsoft.Azure.Documents.Error { Code = "SDK_invariant_violated_4795CC37", Message = errorMessage }); } if (childRanges.Count == 1) { // On a merge, the 410/1002 results in a single parent // We maintain the current enumerator's range and let the RequestInvokerHandler logic kick in enumerators.Enqueue(currentPaginator); } else { // Split foreach (FeedRangeInternal childRange in childRanges) { PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator( new FeedRangeState <TState>(childRange, currentPaginator.FeedRangeState.State)); enumerators.Enqueue(childPaginator); } } // Recursively retry return(await this.MoveNextAsync(childTrace)); } // Just enqueue the paginator and the user can decide if they want to retry. enumerators.Enqueue(currentPaginator); this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromException(currentPaginator.Current.Exception); this.CurrentRange = currentPaginator.FeedRangeState.FeedRange; this.nextState = CrossPartitionRangePageAsyncEnumerator <TPage, TState> .GetNextRange(enumerators); return(true); } if (currentPaginator.FeedRangeState.State != default) { // Don't enqueue the paginator otherwise it's an infinite loop. enumerators.Enqueue(currentPaginator); } CrossFeedRangeState <TState> crossPartitionState; if (enumerators.Count == 0) { crossPartitionState = null; } else { FeedRangeState <TState>[] feedRangeAndStates = new FeedRangeState <TState> [enumerators.Count]; int i = 0; foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators) { feedRangeAndStates[i++] = enumerator.FeedRangeState; } crossPartitionState = new CrossFeedRangeState <TState>(feedRangeAndStates); } this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromResult( new CrossFeedRangePage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState)); this.CurrentRange = currentPaginator.FeedRangeState.FeedRange; this.nextState = CrossPartitionRangePageAsyncEnumerator <TPage, TState> .GetNextRange(enumerators); return(true); } }
public async Task ChangeFeedIteratorCore_Retries() { string continuation = "TBD"; ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK); responseMessage.Headers.ETag = continuation; responseMessage.Headers[Documents.HttpConstants.HttpHeaders.ItemCount] = "1"; Mock <CosmosClientContext> cosmosClientContext = new Mock <CosmosClientContext>(); cosmosClientContext.Setup(c => c.ClientOptions).Returns(new CosmosClientOptions()); cosmosClientContext .Setup(c => c.ProcessResourceOperationStreamAsync( It.IsAny <string>(), It.IsAny <Documents.ResourceType>(), It.IsAny <Documents.OperationType>(), It.IsAny <RequestOptions>(), It.IsAny <ContainerInternal>(), It.IsAny <PartitionKey?>(), It.IsAny <Stream>(), It.IsAny <Action <RequestMessage> >(), It.IsAny <CosmosDiagnosticsContext>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(responseMessage)); ContainerInternal containerCore = Mock.Of <ContainerInternal>(); Mock.Get(containerCore) .Setup(c => c.ClientContext) .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of <FeedRangeInternal>(); Mock.Get(range) .Setup(f => f.Accept(It.IsAny <FeedRangeRequestMessagePopulatorVisitor>())); FeedRangeContinuation feedToken = Mock.Of <FeedRangeContinuation>(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); Mock.Get(feedToken) .SetupSequence(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(Documents.ShouldRetryResult.RetryAfter(TimeSpan.Zero))) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); Mock.Get(feedToken) .SetupSequence(f => f.HandleChangeFeedNotModified(It.IsAny <ResponseMessage>())) .Returns(Documents.ShouldRetryResult.RetryAfter(TimeSpan.Zero)) .Returns(Documents.ShouldRetryResult.NoRetry()) .Returns(Documents.ShouldRetryResult.NoRetry()); ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); Mock.Get(feedToken) .Verify(f => f.ReplaceContinuation(It.IsAny <string>()), Times.Once); Mock.Get(feedToken) .Verify(f => f.HandleSplitAsync(It.Is <ContainerInternal>(c => c == containerCore), It.IsAny <ResponseMessage>(), It.IsAny <CancellationToken>()), Times.Exactly(2)); Mock.Get(feedToken) .Verify(f => f.HandleChangeFeedNotModified(It.IsAny <ResponseMessage>()), Times.Exactly(3)); Mock.Get(cosmosClientContext.Object) .Verify(c => c.ProcessResourceOperationStreamAsync( It.IsAny <string>(), It.IsAny <Documents.ResourceType>(), It.IsAny <Documents.OperationType>(), It.IsAny <RequestOptions>(), It.IsAny <ContainerInternal>(), It.IsAny <PartitionKey?>(), It.IsAny <Stream>(), It.IsAny <Action <RequestMessage> >(), It.IsAny <CosmosDiagnosticsContext>(), It.IsAny <CancellationToken>()), Times.Exactly(3)); }
protected PartitionRangePageAsyncEnumerator(FeedRangeInternal range, CancellationToken cancellationToken, TState state = default) { this.Range = range ?? throw new ArgumentNullException(nameof(range)); this.State = state; this.cancellationToken = cancellationToken; }
public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) { this.Etag = etag; this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); }
public static QueryIterator Create( ContainerCore containerCore, CosmosQueryClient client, CosmosClientContext clientContext, SqlQuerySpec sqlQuerySpec, string continuationToken, FeedRangeInternal feedRangeInternal, QueryRequestOptions queryRequestOptions, string resourceLink, bool isContinuationExpected, bool allowNonValueAggregateQuery, bool forcePassthrough, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo) { if (queryRequestOptions == null) { queryRequestOptions = new QueryRequestOptions(); } CosmosQueryContextCore cosmosQueryContext = new CosmosQueryContextCore( client: client, resourceTypeEnum: Documents.ResourceType.Document, operationType: Documents.OperationType.Query, resourceType: typeof(QueryResponseCore), resourceLink: resourceLink, isContinuationExpected: isContinuationExpected, allowNonValueAggregateQuery: allowNonValueAggregateQuery, correlatedActivityId: Guid.NewGuid()); NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( containerCore, client, queryRequestOptions); DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); CosmosElement requestContinuationToken; switch (queryRequestOptions.ExecutionEnvironment.GetValueOrDefault(ExecutionEnvironment.Client)) { case ExecutionEnvironment.Client: if (continuationToken != null) { TryCatch <CosmosElement> tryParse = CosmosElement.Monadic.Parse(continuationToken); if (tryParse.Failed) { return(new QueryIterator( cosmosQueryContext, new FaultedQueryPipelineStage( new MalformedContinuationTokenException( message: $"Malformed Continuation Token: {continuationToken}", innerException: tryParse.Exception)), queryRequestOptions.CosmosSerializationFormatOptions, queryRequestOptions, clientContext)); } requestContinuationToken = tryParse.Result; } else { requestContinuationToken = null; } break; case ExecutionEnvironment.Compute: requestContinuationToken = queryRequestOptions.CosmosElementContinuationToken; break; default: throw new ArgumentOutOfRangeException($"Unknown {nameof(ExecutionEnvironment)}: {queryRequestOptions.ExecutionEnvironment.Value}."); } CosmosQueryExecutionContextFactory.InputParameters inputParameters = new CosmosQueryExecutionContextFactory.InputParameters( sqlQuerySpec: sqlQuerySpec, initialUserContinuationToken: requestContinuationToken, initialFeedRange: feedRangeInternal, maxConcurrency: queryRequestOptions.MaxConcurrency, maxItemCount: queryRequestOptions.MaxItemCount, maxBufferedItemCount: queryRequestOptions.MaxBufferedItemCount, partitionKey: queryRequestOptions.PartitionKey, properties: queryRequestOptions.Properties, partitionedQueryExecutionInfo: partitionedQueryExecutionInfo, executionEnvironment: queryRequestOptions.ExecutionEnvironment, returnResultsInDeterministicOrder: queryRequestOptions.ReturnResultsInDeterministicOrder, forcePassthrough: forcePassthrough, testInjections: queryRequestOptions.TestSettings); return(new QueryIterator( cosmosQueryContext, CosmosQueryExecutionContextFactory.Create(documentContainer, cosmosQueryContext, inputParameters, NoOpTrace.Singleton), queryRequestOptions.CosmosSerializationFormatOptions, queryRequestOptions, clientContext)); }
/// <summary> /// Initializes an instance of the <see cref="ChangeFeedStartFromBeginning"/> class. /// </summary> /// <param name="feedRange">The (optional) range to start from.</param> public ChangeFeedStartFromBeginning(FeedRangeInternal feedRange) : base() { this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); }
public Task <TryCatch> MonadicSplitAsync( FeedRangeInternal feedRange, CancellationToken cancellationToken) => this.monadicDocumentContainer.MonadicSplitAsync( feedRange, cancellationToken);
public ChangeFeedContinuationToken(FeedRangeInternal feedRange, ChangeFeedState changeFeedState) { this.Range = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); this.State = changeFeedState ?? throw new ArgumentNullException(nameof(changeFeedState)); }