public FeedRangeCompositeContinuation( string containerRid, FeedRangeInternal feedRange, IReadOnlyList <Documents.Routing.Range <string> > ranges, string continuation = null) : this(containerRid, feedRange) { if (ranges == null) { throw new ArgumentNullException(nameof(ranges)); } if (ranges.Count == 0) { throw new ArgumentOutOfRangeException(nameof(ranges)); } foreach (Documents.Routing.Range <string> range in ranges) { this.CompositeContinuationTokens.Enqueue( FeedRangeCompositeContinuation.CreateCompositeContinuationTokenForRange( range.Min, range.Max, continuation)); } this.CurrentToken = this.CompositeContinuationTokens.Peek(); }
public void MoveToNextToken() { CompositeContinuationToken recentToken = this.compositeContinuationTokens.Dequeue(); this.compositeContinuationTokens.Enqueue(recentToken); this.currentToken = this.compositeContinuationTokens.Peek(); }
public void TestMatchRangesTocontinuationTokens_OneToNone() { PartitionKeyRange partitionKeyRange = new PartitionKeyRange() { MinInclusive = string.Empty, MaxExclusive = "A", Id = "1" }; CompositeContinuationToken token = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: "B", max: "C", isMinInclusive: true, isMaxInclusive: false), Token = "asdf" }; IReadOnlyDictionary <PartitionKeyRange, IPartitionedToken> expectedMapping = new Dictionary <PartitionKeyRange, IPartitionedToken>() { { partitionKeyRange, null }, }; ContinuationResumeLogicTests.RunMatchRangesToContinuationTokens( expectedMapping, new PartitionKeyRange[] { partitionKeyRange }, new CompositeContinuationToken[] { token }); }
public FeedTokenEPKRange( string containerRid, Documents.Routing.Range <string> completeRange, IReadOnlyList <CompositeContinuationToken> deserializedTokens) : this(containerRid) { if (deserializedTokens == null) { throw new ArgumentNullException(nameof(deserializedTokens)); } if (completeRange == null) { throw new ArgumentNullException(nameof(completeRange)); } if (deserializedTokens.Count == 0) { throw new ArgumentOutOfRangeException(nameof(deserializedTokens)); } this.CompleteRange = completeRange; foreach (CompositeContinuationToken token in deserializedTokens) { this.CompositeContinuationTokens.Enqueue(token); } this.currentToken = this.CompositeContinuationTokens.Peek(); }
private static bool TryParseAsCompositeContinuationToken( string providedContinuation, out CompositeContinuationToken compositeContinuationToken) { compositeContinuationToken = null; try { if (providedContinuation.Trim().StartsWith("[", StringComparison.Ordinal)) { List <CompositeContinuationToken> compositeContinuationTokens = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(providedContinuation); if (compositeContinuationTokens != null && compositeContinuationTokens.Count > 0) { compositeContinuationToken = compositeContinuationTokens[0]; } return(compositeContinuationToken != null); } else if (providedContinuation.Trim().StartsWith("{", StringComparison.Ordinal)) { compositeContinuationToken = JsonConvert.DeserializeObject <CompositeContinuationToken>(providedContinuation); return(compositeContinuationToken != null); } return(false); } catch (JsonException) { return(false); } }
public void TestTryGetInitializationInfo_ResumeRightPartition() { PartitionKeyRange pkRange1 = new PartitionKeyRange() { MinInclusive = string.Empty, MaxExclusive = "A", Id = "1" }; PartitionKeyRange pkRange2 = new PartitionKeyRange() { MinInclusive = "A", MaxExclusive = "B", Id = "2" }; PartitionKeyRange pkRange3 = new PartitionKeyRange() { MinInclusive = "B", MaxExclusive = "C", Id = "3" }; CompositeContinuationToken token = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: "B", max: "C", isMinInclusive: true, isMaxInclusive: false), Token = "asdf" }; IReadOnlyDictionary <PartitionKeyRange, IPartitionedToken> expectedMappingLeftPartitions = new Dictionary <PartitionKeyRange, IPartitionedToken>() { { pkRange1, null }, { pkRange2, null }, }; IReadOnlyDictionary <PartitionKeyRange, IPartitionedToken> expectedMappingTargetPartition = new Dictionary <PartitionKeyRange, IPartitionedToken>() { { pkRange3, token }, }; IReadOnlyDictionary <PartitionKeyRange, IPartitionedToken> expectedMappingRightPartitions = new Dictionary <PartitionKeyRange, IPartitionedToken>() { }; RunTryGetInitializationInfo( expectedMappingLeftPartitions, expectedMappingTargetPartition, expectedMappingRightPartitions, new PartitionKeyRange[] { pkRange1, pkRange2, pkRange3 }, new IPartitionedToken[] { token }); }
private void InitializeCompositeTokens(IEnumerable <CompositeContinuationToken> tokens) { this.compositeContinuationTokens = new Queue <CompositeContinuationToken>(); foreach (CompositeContinuationToken token in tokens) { this.compositeContinuationTokens.Enqueue(token); } this.currentToken = this.compositeContinuationTokens.Peek(); }
private void MoveToNextToken() { CompositeContinuationToken recentToken = this.CompositeContinuationTokens.Dequeue(); if (recentToken.Token != null) { // Normal ReadFeed can signal termination by CT null, not NotModified // Change Feed never lands here, as it always provides a CT // Consider current range done, if this FeedToken contains multiple ranges due to splits, all of them need to be considered done this.CompositeContinuationTokens.Enqueue(recentToken); } this.CurrentToken = this.CompositeContinuationTokens.Count > 0 ? this.CompositeContinuationTokens.Peek() : null; }
private void MoveToNextToken() { CompositeContinuationToken recentToken = this.CompositeContinuationTokens.Dequeue(); this.CompositeContinuationTokens.Enqueue(recentToken); this.CurrentToken = this.CompositeContinuationTokens.Peek(); // In a Query / ReadFeed not Change Feed, skip ranges that are done to avoid requests while (!this.IsDone && this.doneRanges.Contains(this.CurrentToken.Range.Min)) { this.MoveToNextToken(); } }
public FeedTokenEPKRange( string containerRid, Documents.PartitionKeyRange keyRange) : this(containerRid) { if (keyRange == null) { throw new ArgumentNullException(nameof(keyRange)); } this.CompleteRange = new Documents.Routing.Range <string>(keyRange.MinInclusive, keyRange.MaxExclusive, true, false); this.CompositeContinuationTokens.Enqueue(FeedTokenEPKRange.CreateCompositeContinuationTokenForRange(keyRange.MinInclusive, keyRange.MaxExclusive, null)); this.currentToken = this.CompositeContinuationTokens.Peek(); }
private FeedTokenEPKRange( string containerRid, CompositeContinuationToken compositeContinuationTokenByPartitionKeyRangeId) : this(containerRid) { if (compositeContinuationTokenByPartitionKeyRangeId == null) { throw new ArgumentNullException(nameof(compositeContinuationTokenByPartitionKeyRangeId)); } this.CompleteRange = compositeContinuationTokenByPartitionKeyRangeId.Range; this.CompositeContinuationTokens.Enqueue(compositeContinuationTokenByPartitionKeyRangeId); this.currentToken = this.CompositeContinuationTokens.Peek(); }
public void CompositeContinuationTokenIsNotPassedToBackend() { Range <string> expectedRange = new Range <string>("A", "B", true, false); string expectedToken = "someToken"; CompositeContinuationToken compositeContinuationToken = new CompositeContinuationToken { Range = expectedRange, Token = expectedToken }; string continuation = JsonConvert.SerializeObject(compositeContinuationToken); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); DictionaryNameValueCollection headers = new DictionaryNameValueCollection(); headers.Add(HttpConstants.HttpHeaders.Continuation, continuation); Range <string> range = partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(headers, out List <CompositeContinuationToken> compositeContinuationTokens); Assert.IsTrue(expectedRange.Equals(range)); Assert.AreEqual(expectedToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); //not a composite token }
public FeedTokenEPKRange( string containerRid, IReadOnlyList <Documents.PartitionKeyRange> keyRanges) : this(containerRid) { if (keyRanges == null) { throw new ArgumentNullException(nameof(keyRanges)); } if (keyRanges.Count == 0) { throw new ArgumentOutOfRangeException(nameof(keyRanges)); } this.CompleteRange = new Documents.Routing.Range <string>(keyRanges[0].MinInclusive, keyRanges[keyRanges.Count - 1].MaxExclusive, true, false); foreach (Documents.PartitionKeyRange keyRange in keyRanges) { this.CompositeContinuationTokens.Enqueue(FeedTokenEPKRange.CreateCompositeContinuationTokenForRange(keyRange.MinInclusive, keyRange.MaxExclusive, null)); } this.currentToken = this.CompositeContinuationTokens.Peek(); }
/// <summary> /// Used for deserialization only /// </summary> public FeedRangeCompositeContinuation( string containerRid, FeedRangeInternal feedRange, IReadOnlyList <CompositeContinuationToken> deserializedTokens) : this(containerRid, feedRange) { if (deserializedTokens == null) { throw new ArgumentNullException(nameof(deserializedTokens)); } if (deserializedTokens.Count == 0) { throw new ArgumentOutOfRangeException(nameof(deserializedTokens)); } foreach (CompositeContinuationToken token in deserializedTokens) { this.CompositeContinuationTokens.Enqueue(token); } this.CurrentToken = this.CompositeContinuationTokens.Peek(); }
public async Task GetChangeFeedTokensAsyncReturnsOnePerPartitionKeyRange() { // Setting mock to have 3 ranges, to generate 3 tokens MultiRangeMockDocumentClient documentClient = new MultiRangeMockDocumentClient(); using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); Mock <CosmosClientContext> mockContext = new Mock <CosmosClientContext>(); mockContext.Setup(x => x.ClientOptions).Returns(MockCosmosUtil.GetDefaultConfiguration()); mockContext.Setup(x => x.DocumentClient).Returns(documentClient); mockContext.Setup(x => x.SerializerCore).Returns(MockCosmosUtil.Serializer); mockContext.Setup(x => x.Client).Returns(client); mockContext.Setup(x => x.CreateLink(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())).Returns(UriFactory.CreateDocumentCollectionUri("test", "test").OriginalString); DatabaseInternal db = new DatabaseInlineCore(mockContext.Object, "test"); ContainerInternal container = new ContainerInlineCore(mockContext.Object, db, "test"); IEnumerable <string> tokens = await container.GetChangeFeedTokensAsync(); Assert.AreEqual(3, tokens.Count()); PartitionKeyRangeCache pkRangeCache = await documentClient.GetPartitionKeyRangeCacheAsync(); foreach (string token in tokens) { // Validate that each token represents a StandByFeedContinuationToken with a single Range List <CompositeContinuationToken> deserialized = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(token); Assert.AreEqual(1, deserialized.Count); CompositeContinuationToken compositeToken = deserialized[0]; IReadOnlyList <Documents.PartitionKeyRange> rangesForTheToken = await pkRangeCache.TryGetOverlappingRangesAsync("", compositeToken.Range); // Token represents one range Assert.AreEqual(1, rangesForTheToken.Count); Assert.AreEqual(rangesForTheToken[0].MinInclusive, compositeToken.Range.Min); Assert.AreEqual(rangesForTheToken[0].MaxExclusive, compositeToken.Range.Max); } }
public async Task TestCosmosOrderByQueryExecutionContextWithFailurePageAsync(bool createInitialContinuationToken) { int maxPageSize = 5; List <MockPartitionResponse[]> mockResponsesScenario = MockQueryFactory.GetFailureScenarios(); Mock <CosmosQueryClient> mockQueryClient = new Mock <CosmosQueryClient>(); foreach (MockPartitionResponse[] mockResponse in mockResponsesScenario) { string initialContinuationToken = null; string fullContinuationToken = null; if (createInitialContinuationToken) { ToDoItem itemToRepresentPreviousQuery = ToDoItem.CreateItems( 1, "itemToRepresentPreviousQuery", MockQueryFactory.DefaultCollectionRid).First(); initialContinuationToken = $" - RID:{itemToRepresentPreviousQuery._rid} ==#RT:1#TRC:1"; CompositeContinuationToken compositeContinuation = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: MockQueryFactory.DefaultPartitionKeyRange.MinInclusive, max: MockQueryFactory.DefaultPartitionKeyRange.MaxExclusive, isMaxInclusive: false, isMinInclusive: true), Token = initialContinuationToken }; List <OrderByItem> orderByItems = new List <OrderByItem>() { new OrderByItem(CosmosObject.CreateFromBuffer(Encoding.UTF8.GetBytes("{\"item\":\"2c4ce711-13c3-4c93-817c-49287b71b6c3\"}"))) }; OrderByContinuationToken orderByContinuationToken = new OrderByContinuationToken( compositeContinuationToken: compositeContinuation, orderByItems: orderByItems, rid: itemToRepresentPreviousQuery._rid, skipCount: 0, filter: null); fullContinuationToken = CosmosArray.Create( new List <CosmosElement>() { OrderByContinuationToken.ToCosmosElement(orderByContinuationToken) }).ToString(); } IList <ToDoItem> allItems = MockQueryFactory.GenerateAndMockResponse( mockQueryClient, isOrderByQuery: true, sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, containerRid: MockQueryFactory.DefaultCollectionRid, initContinuationToken: initialContinuationToken, maxPageSize: maxPageSize, mockResponseForSinglePartition: mockResponse, cancellationTokenForMocks: this.cancellationToken); // Order by drains the partitions until it finds an item // If there are no items then it's not possible to have a continuation token if (allItems.Count == 0 && createInitialContinuationToken) { continue; } CosmosQueryContext context = MockQueryFactory.CreateContext( mockQueryClient.Object); QueryInfo queryInfo = new QueryInfo() { OrderBy = new SortOrder[] { SortOrder.Ascending }, OrderByExpressions = new string[] { "id" } }; CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, collectionRid: MockQueryFactory.DefaultCollectionRid, partitionedQueryExecutionInfo: new PartitionedQueryExecutionInfo() { QueryInfo = queryInfo }, partitionKeyRanges: new List <PartitionKeyRange>() { MockQueryFactory.DefaultPartitionKeyRange }, initialPageSize: maxPageSize, maxConcurrency: null, maxItemCount: maxPageSize, maxBufferedItemCount: null, returnResultsInDeterministicOrder: true, testSettings: new TestInjections(simulate429s: false, simulateEmptyPages: false)); TryCatch <IDocumentQueryExecutionComponent> tryCreate = await CosmosOrderByItemQueryExecutionContext.TryCreateAsync( context, initParams, fullContinuationToken != null?CosmosElement.Parse(fullContinuationToken) : null, this.cancellationToken); if (tryCreate.Succeeded) { IDocumentQueryExecutionComponent executionContext = tryCreate.Result; Assert.IsTrue(!executionContext.IsDone); // Read all the pages from both splits List <ToDoItem> itemsRead = new List <ToDoItem>(); QueryResponseCore?failure = null; while (!executionContext.IsDone) { QueryResponseCore queryResponse = await executionContext.DrainAsync( maxPageSize, this.cancellationToken); if (queryResponse.IsSuccess) { string responseContinuationToken = queryResponse.ContinuationToken; foreach (CosmosElement element in queryResponse.CosmosElements) { string jsonValue = element.ToString(); ToDoItem item = JsonConvert.DeserializeObject <ToDoItem>(jsonValue); itemsRead.Add(item); } } else { Assert.IsNull(failure, "There should only be one error"); failure = queryResponse; } } Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(0 /*We don't get any items, since we don't buffer the failure anymore*/, itemsRead.Count); //CollectionAssert.AreEqual(allItems.ToList(), itemsRead, new ToDoItemComparer()); } else { QueryResponseCore queryResponseCore = QueryResponseFactory.CreateFromException(tryCreate.Exception); Assert.AreEqual((HttpStatusCode)429, queryResponseCore.StatusCode); } } }
private void AddFormattedContinuationHeaderHelper(AddFormattedContinuationToHeaderTestUnit positiveTestData, out INameValueCollection headers, out List <PartitionKeyRange> resolvedRanges, out List <CompositeContinuationToken> resolvedContinuationTokens) { Func <string, INameValueCollection> getHeadersWithContinuation = (string continuationToken) => { INameValueCollection localHeaders = new StoreRequestNameValueCollection(); if (continuationToken != null) { localHeaders[HttpConstants.HttpHeaders.Continuation] = continuationToken; } return(localHeaders); }; resolvedRanges = positiveTestData.ResolvedRanges.Select(x => new PartitionKeyRange() { MinInclusive = x.Min, MaxExclusive = x.Max }).ToList(); resolvedContinuationTokens = new List <CompositeContinuationToken>(); CompositeContinuationToken[] initialContinuationTokens = null; if (!string.IsNullOrEmpty(positiveTestData.InputCompositeContinuationToken)) { if (positiveTestData.InputCompositeContinuationToken.Trim().StartsWith("[", StringComparison.Ordinal)) { initialContinuationTokens = JsonConvert.DeserializeObject <CompositeContinuationToken[]>(positiveTestData.InputCompositeContinuationToken); } else { initialContinuationTokens = new CompositeContinuationToken[] { JsonConvert.DeserializeObject <CompositeContinuationToken>(positiveTestData.InputCompositeContinuationToken) }; } } if (resolvedRanges.Count > 1) { CompositeContinuationToken continuationToBeCopied; if (initialContinuationTokens != null && initialContinuationTokens.Length > 0) { continuationToBeCopied = (CompositeContinuationToken)initialContinuationTokens[0].ShallowCopy(); } else { continuationToBeCopied = new CompositeContinuationToken(); continuationToBeCopied.Token = string.Empty; } headers = getHeadersWithContinuation(continuationToBeCopied.Token); foreach (PartitionKeyRange pkrange in resolvedRanges) { CompositeContinuationToken token = (CompositeContinuationToken)continuationToBeCopied.ShallowCopy(); token.Range = pkrange.ToRange(); resolvedContinuationTokens.Add(token); } if (initialContinuationTokens != null) { resolvedContinuationTokens.AddRange(initialContinuationTokens.Skip(1)); } } else { headers = getHeadersWithContinuation(null); } }
private static TryCatch <IReadOnlyList <CompositeContinuationToken> > TryParseCompositeContinuationList( CosmosElement requestContinuationToken) { if (requestContinuationToken == null) { throw new ArgumentNullException(nameof(requestContinuationToken)); } if (!(requestContinuationToken is CosmosArray compositeContinuationTokenListRaw)) { return(TryCatch <IReadOnlyList <CompositeContinuationToken> > .FromException( new MalformedContinuationTokenException( $"Invalid format for continuation token {requestContinuationToken} for {nameof(CosmosParallelItemQueryExecutionContext)}"))); } List <CompositeContinuationToken> compositeContinuationTokens = new List <CompositeContinuationToken>(); foreach (CosmosElement compositeContinuationTokenRaw in compositeContinuationTokenListRaw) { TryCatch <CompositeContinuationToken> tryCreateCompositeContinuationToken = CompositeContinuationToken.TryCreateFromCosmosElement(compositeContinuationTokenRaw); if (!tryCreateCompositeContinuationToken.Succeeded) { return(TryCatch <IReadOnlyList <CompositeContinuationToken> > .FromException(tryCreateCompositeContinuationToken.Exception)); } compositeContinuationTokens.Add(tryCreateCompositeContinuationToken.Result); } return(TryCatch <IReadOnlyList <CompositeContinuationToken> > .FromResult(compositeContinuationTokens)); }
public override async Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = await this.lazyMonadicEnumerator.GetValueAsync(cancellationToken); if (monadicEnumerator.Failed) { Exception createException = monadicEnumerator.Exception; CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(createException); return(new ResponseMessage( cosmosException.StatusCode, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, diagnostics: new CosmosDiagnosticsContextCore())); } CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result; if (!await enumerator.MoveNextAsync()) { throw new InvalidOperationException("ChangeFeed enumerator should always have a next continuation"); } if (enumerator.Current.Failed) { CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(enumerator.Current.Exception); if (!IsRetriableException(cosmosException)) { this.hasMoreResults = false; } return(new ResponseMessage( cosmosException.StatusCode, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, diagnostics: new CosmosDiagnosticsContextCore())); } CrossFeedRangePage <Pagination.ChangeFeedPage, ChangeFeedState> crossFeedRangePage = enumerator.Current.Result; Pagination.ChangeFeedPage changeFeedPage = crossFeedRangePage.Page; ResponseMessage responseMessage; if (changeFeedPage is Pagination.ChangeFeedSuccessPage changeFeedSuccessPage) { responseMessage = new ResponseMessage(statusCode: System.Net.HttpStatusCode.OK) { Content = changeFeedSuccessPage.Content }; } else { responseMessage = new ResponseMessage(statusCode: System.Net.HttpStatusCode.NotModified); } CrossFeedRangeState <ChangeFeedState> crossFeedRangeState = crossFeedRangePage.State; string continuationToken; if (this.changeFeedRequestOptions.EmitOldContinuationToken) { List <CompositeContinuationToken> compositeContinuationTokens = new List <CompositeContinuationToken>(); for (int i = 0; i < crossFeedRangeState.Value.Length; i++) { FeedRangeState <ChangeFeedState> changeFeedFeedRangeState = crossFeedRangeState.Value.Span[i]; string token = changeFeedFeedRangeState.State is ChangeFeedStateContinuation changeFeedStateContinuation ? ((CosmosString)changeFeedStateContinuation.ContinuationToken).Value : null; Documents.Routing.Range <string> range = ((FeedRangeEpk)changeFeedFeedRangeState.FeedRange).Range; CompositeContinuationToken compositeContinuationToken = new CompositeContinuationToken() { Range = range, Token = token, }; compositeContinuationTokens.Add(compositeContinuationToken); } FeedRangeCompositeContinuation feedRangeCompositeContinuationToken = new FeedRangeCompositeContinuation( await this.documentContainer.GetResourceIdentifierAsync(cancellationToken), FeedRangeEpk.FullRange, compositeContinuationTokens); continuationToken = feedRangeCompositeContinuationToken.ToString(); } else { ChangeFeedCrossFeedRangeState changeFeedCrossFeedRangeState = new ChangeFeedCrossFeedRangeState(crossFeedRangeState.Value); continuationToken = VersionedAndRidCheckedCompositeToken.ToCosmosElement( new VersionedAndRidCheckedCompositeToken( VersionedAndRidCheckedCompositeToken.Version.V2, changeFeedCrossFeedRangeState.ToCosmosElement(), await this.documentContainer.GetResourceIdentifierAsync(cancellationToken))).ToString(); } responseMessage.Headers.ContinuationToken = continuationToken; responseMessage.Headers.RequestCharge = changeFeedPage.RequestCharge; responseMessage.Headers.ActivityId = changeFeedPage.ActivityId; return(responseMessage); }
public override async Task <ResponseMessage> ReadNextAsync( ITrace trace, CancellationToken cancellationToken = default) { if (trace == null) { throw new ArgumentNullException(nameof(trace)); } cancellationToken.ThrowIfCancellationRequested(); if (!this.hasMoreResults) { throw new InvalidOperationException("Should not be calling FeedIterator that does not have any more results"); } if (this.monadicEnumerator.Failed) { this.hasMoreResults = false; CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(this.monadicEnumerator.Exception); return(new ResponseMessage( statusCode: System.Net.HttpStatusCode.BadRequest, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, trace: trace)); } CrossPartitionReadFeedAsyncEnumerator enumerator = this.monadicEnumerator.Result; TryCatch <CrossFeedRangePage <Pagination.ReadFeedPage, ReadFeedState> > monadicPage; try { if (!await enumerator.MoveNextAsync(trace)) { throw new InvalidOperationException("Should not be calling enumerator that does not have any more results"); } monadicPage = enumerator.Current; } catch (Exception ex) { monadicPage = TryCatch <CrossFeedRangePage <Pagination.ReadFeedPage, ReadFeedState> > .FromException(ex); } if (monadicPage.Failed) { CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(monadicPage.Exception); if (!IsRetriableException(cosmosException)) { this.hasMoreResults = false; } return(new ResponseMessage( statusCode: cosmosException.StatusCode, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, trace: trace)); } CrossFeedRangePage <Pagination.ReadFeedPage, ReadFeedState> crossFeedRangePage = monadicPage.Result; if (crossFeedRangePage.State == default) { this.hasMoreResults = false; } // Make the continuation token match the older format: string continuationToken; if (crossFeedRangePage.State != null) { List <CompositeContinuationToken> compositeContinuationTokens = new List <CompositeContinuationToken>(); CrossFeedRangeState <ReadFeedState> crossFeedRangeState = crossFeedRangePage.State; for (int i = 0; i < crossFeedRangeState.Value.Length; i++) { FeedRangeState <ReadFeedState> feedRangeState = crossFeedRangeState.Value.Span[i]; FeedRangeEpk feedRange; if (feedRangeState.FeedRange is FeedRangeEpk feedRangeEpk) { feedRange = feedRangeEpk; } else { feedRange = FeedRangeEpk.FullRange; } ReadFeedState readFeedState = feedRangeState.State; CompositeContinuationToken compositeContinuationToken = new CompositeContinuationToken() { Range = feedRange.Range, Token = readFeedState is ReadFeedBeginningState ? null : ((ReadFeedContinuationState)readFeedState).ContinuationToken.ToString(), }; compositeContinuationTokens.Add(compositeContinuationToken); } FeedRangeInternal outerFeedRange; if ((this.queryRequestOptions != null) && this.queryRequestOptions.PartitionKey.HasValue) { outerFeedRange = new FeedRangePartitionKey(this.queryRequestOptions.PartitionKey.Value); } else if ((this.queryRequestOptions != null) && (this.queryRequestOptions.FeedRange != null)) { outerFeedRange = (FeedRangeInternal)this.queryRequestOptions.FeedRange; } else { outerFeedRange = FeedRangeEpk.FullRange; } FeedRangeCompositeContinuation feedRangeCompositeContinuation = new FeedRangeCompositeContinuation( containerRid: string.Empty, feedRange: outerFeedRange, compositeContinuationTokens); continuationToken = feedRangeCompositeContinuation.ToString(); } else { continuationToken = null; } Pagination.ReadFeedPage page = crossFeedRangePage.Page; Headers headers = new Headers() { RequestCharge = page.RequestCharge, ActivityId = page.ActivityId, ContinuationToken = continuationToken, }; foreach (KeyValuePair <string, string> kvp in page.AdditionalHeaders) { headers[kvp.Key] = kvp.Value; } return(new ResponseMessage( statusCode: System.Net.HttpStatusCode.OK, requestMessage: default,
public async Task TestCosmosCrossPartitionQueryExecutionContextWithFailuresAsync(bool createInitialContinuationToken) { int maxPageSize = 5; List <MockPartitionResponse[]> mockResponsesScenario = MockQueryFactory.GetFailureScenarios(); foreach (MockPartitionResponse[] mockResponse in mockResponsesScenario) { string initialContinuationToken = null; string fullContinuationToken = null; if (createInitialContinuationToken) { initialContinuationToken = " - RID:02FYAIvUH1kCAAAAAAAAAA ==#RT:1#TRC:1"; CompositeContinuationToken compositeContinuation = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: MockQueryFactory.DefaultPartitionKeyRange.MinInclusive, max: MockQueryFactory.DefaultPartitionKeyRange.MaxExclusive, isMaxInclusive: false, isMinInclusive: true), Token = initialContinuationToken }; fullContinuationToken = CosmosArray.Create( new List <CosmosElement>() { CompositeContinuationToken.ToCosmosElement(compositeContinuation) }).ToString(); } Mock <CosmosQueryClient> mockQueryClient = new Mock <CosmosQueryClient>(); IList <ToDoItem> allItems = MockQueryFactory.GenerateAndMockResponse( mockQueryClient, isOrderByQuery: false, sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, containerRid: MockQueryFactory.DefaultCollectionRid, initContinuationToken: initialContinuationToken, maxPageSize: maxPageSize, mockResponseForSinglePartition: mockResponse, cancellationTokenForMocks: this.cancellationToken); CosmosQueryContext context = MockQueryFactory.CreateContext( mockQueryClient.Object); CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, collectionRid: MockQueryFactory.DefaultCollectionRid, partitionedQueryExecutionInfo: new PartitionedQueryExecutionInfo() { QueryInfo = new QueryInfo() }, partitionKeyRanges: new List <PartitionKeyRange>() { MockQueryFactory.DefaultPartitionKeyRange }, initialPageSize: maxPageSize, maxConcurrency: null, maxItemCount: maxPageSize, maxBufferedItemCount: null, returnResultsInDeterministicOrder: true, testSettings: new TestInjections(simulate429s: false, simulateEmptyPages: false)); IDocumentQueryExecutionComponent executionContext = (await CosmosParallelItemQueryExecutionContext.TryCreateAsync( context, initParams, fullContinuationToken != null ? CosmosElement.Parse(fullContinuationToken) : null, this.cancellationToken)).Result; // Read all the pages from both splits List <ToDoItem> itemsRead = new List <ToDoItem>(); Assert.IsTrue(!executionContext.IsDone); QueryResponseCore?failure = null; while (!executionContext.IsDone) { QueryResponseCore queryResponse = await executionContext.DrainAsync(maxPageSize, this.cancellationToken); if (queryResponse.IsSuccess) { string responseContinuationToken = queryResponse.ContinuationToken; foreach (CosmosElement element in queryResponse.CosmosElements) { string jsonValue = element.ToString(); ToDoItem item = JsonConvert.DeserializeObject <ToDoItem>(jsonValue); itemsRead.Add(item); } } else { Assert.IsNull(failure, "There should only be one error"); failure = queryResponse; } } Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(allItems.Count, itemsRead.Count); List <ToDoItem> exepected = allItems.OrderBy(x => x.id).ToList(); List <ToDoItem> actual = itemsRead.OrderBy(x => x.id).ToList(); CollectionAssert.AreEqual(exepected, actual, new ToDoItemComparer()); } }
public async Task TestCosmosCrossPartitionQueryExecutionContextWithEmptyPagesAndSplitAsync(bool createInitialContinuationToken) { int maxPageSize = 5; List <MockPartitionResponse[]> mockResponsesScenario = MockQueryFactory.GetSplitScenarios(); foreach (MockPartitionResponse[] mockResponse in mockResponsesScenario) { string initialContinuationToken = null; string fullConitnuationToken = null; if (createInitialContinuationToken) { initialContinuationToken = " - RID:02FYAIvUH1kCAAAAAAAAAA ==#RT:1#TRC:1"; CompositeContinuationToken compositeContinuation = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: MockQueryFactory.DefaultPartitionKeyRange.MinInclusive, max: MockQueryFactory.DefaultPartitionKeyRange.MaxExclusive, isMaxInclusive: false, isMinInclusive: true), Token = initialContinuationToken }; fullConitnuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken[] { compositeContinuation }); } Mock <CosmosQueryClient> mockQueryClient = new Mock <CosmosQueryClient>(); IList <ToDoItem> allItems = MockQueryFactory.GenerateAndMockResponse( mockQueryClient, isOrderByQuery: false, sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, containerRid: MockQueryFactory.DefaultCollectionRid, initContinuationToken: initialContinuationToken, maxPageSize: maxPageSize, mockResponseForSinglePartition: mockResponse, cancellationTokenForMocks: this.cancellationToken); CosmosQueryContext context = MockQueryFactory.CreateContext( mockQueryClient.Object); CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, collectionRid: MockQueryFactory.DefaultCollectionRid, partitionedQueryExecutionInfo: new PartitionedQueryExecutionInfo() { QueryInfo = new QueryInfo() }, partitionKeyRanges: new List <PartitionKeyRange>() { MockQueryFactory.DefaultPartitionKeyRange }, initialPageSize: maxPageSize, maxConcurrency: null, maxItemCount: maxPageSize, maxBufferedItemCount: null); CosmosParallelItemQueryExecutionContext executionContext = await CosmosParallelItemQueryExecutionContext.CreateAsync( context, initParams, fullConitnuationToken, this.cancellationToken); // Read all the pages from both splits List <ToDoItem> itemsRead = new List <ToDoItem>(); Assert.IsTrue(!executionContext.IsDone); while (!executionContext.IsDone) { QueryResponseCore queryResponse = await executionContext.DrainAsync(maxPageSize, this.cancellationToken); string responseContinuationToken = queryResponse.ContinuationToken; foreach (CosmosElement element in queryResponse.CosmosElements) { string jsonValue = element.ToString(); ToDoItem item = JsonConvert.DeserializeObject <ToDoItem>(jsonValue); itemsRead.Add(item); } } Assert.AreEqual(allItems.Count, itemsRead.Count); List <ToDoItem> exepected = allItems.OrderBy(x => x.id).ToList(); List <ToDoItem> actual = itemsRead.OrderBy(x => x.id).ToList(); CollectionAssert.AreEqual(exepected, actual, new ToDoItemComparer()); } }
public async Task TestCosmosOrderByQueryExecutionContextWithEmptyPagesAndSplitAsync(bool createInitialContinuationToken) { int maxPageSize = 5; List <MockPartitionResponse[]> mockResponsesScenario = MockQueryFactory.GetSplitScenarios(); foreach (MockPartitionResponse[] mockResponse in mockResponsesScenario) { string initialContinuationToken = null; string fullConitnuationToken = null; if (createInitialContinuationToken) { ToDoItem itemToRepresentPreviousQuery = ToDoItem.CreateItems( 1, "itemToRepresentPreviousQuery", MockQueryFactory.DefaultCollectionRid).First(); initialContinuationToken = $" - RID:{itemToRepresentPreviousQuery._rid} ==#RT:1#TRC:1"; CompositeContinuationToken compositeContinuation = new CompositeContinuationToken() { Range = new Documents.Routing.Range <string>( min: MockQueryFactory.DefaultPartitionKeyRange.MinInclusive, max: MockQueryFactory.DefaultPartitionKeyRange.MaxExclusive, isMaxInclusive: false, isMinInclusive: true), Token = initialContinuationToken }; List <OrderByItem> orderByItems = new List <OrderByItem>() { new OrderByItem(CosmosObject.Create(Encoding.UTF8.GetBytes("{\"item\":\"2c4ce711-13c3-4c93-817c-49287b71b6c3\"}"))) }; OrderByContinuationToken orderByContinuationToken = new OrderByContinuationToken( compositeContinuationToken: compositeContinuation, orderByItems: orderByItems, rid: itemToRepresentPreviousQuery._rid, skipCount: 0, filter: null); fullConitnuationToken = JsonConvert.SerializeObject(new OrderByContinuationToken[] { orderByContinuationToken }); } Mock <CosmosQueryClient> mockQueryClient = new Mock <CosmosQueryClient>(); IList <ToDoItem> allItems = MockQueryFactory.GenerateAndMockResponse( mockQueryClient, isOrderByQuery: true, sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, containerRid: MockQueryFactory.DefaultCollectionRid, initContinuationToken: initialContinuationToken, maxPageSize: maxPageSize, mockResponseForSinglePartition: mockResponse, cancellationTokenForMocks: this.cancellationToken); // Order by drains the partitions until it finds an item // If there are no items then it's not possible to have a continuation token if (allItems.Count == 0 && createInitialContinuationToken) { continue; } CosmosQueryContext context = MockQueryFactory.CreateContext( mockQueryClient.Object); QueryInfo queryInfo = new QueryInfo() { OrderBy = new SortOrder[] { SortOrder.Ascending }, OrderByExpressions = new string[] { "id" } }; CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: MockQueryFactory.DefaultQuerySpec, collectionRid: MockQueryFactory.DefaultCollectionRid, partitionedQueryExecutionInfo: new PartitionedQueryExecutionInfo() { QueryInfo = queryInfo }, partitionKeyRanges: new List <PartitionKeyRange>() { MockQueryFactory.DefaultPartitionKeyRange }, initialPageSize: maxPageSize, maxConcurrency: null, maxItemCount: maxPageSize, maxBufferedItemCount: null); CosmosOrderByItemQueryExecutionContext executionContext = await CosmosOrderByItemQueryExecutionContext.CreateAsync( context, initParams, fullConitnuationToken, this.cancellationToken); // For order by it will drain all the pages till it gets a value. if (allItems.Count == 0) { Assert.IsTrue(executionContext.IsDone); continue; } Assert.IsTrue(!executionContext.IsDone); // Read all the pages from both splits List <ToDoItem> itemsRead = new List <ToDoItem>(); while (!executionContext.IsDone) { QueryResponseCore queryResponse = await executionContext.DrainAsync( maxPageSize, this.cancellationToken); string responseContinuationToken = queryResponse.ContinuationToken; foreach (CosmosElement element in queryResponse.CosmosElements) { string jsonValue = element.ToString(); ToDoItem item = JsonConvert.DeserializeObject <ToDoItem>(jsonValue); itemsRead.Add(item); } } Assert.AreEqual(allItems.Count, itemsRead.Count); CollectionAssert.AreEqual(allItems.ToList(), itemsRead, new ToDoItemComparer()); } }
/// <summary> /// Get the next set of results from the cosmos service /// </summary> /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param> /// <returns>A query response from cosmos service</returns> public override async Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (!this.hasMoreResults) { throw new InvalidOperationException("Should not be calling FeedIterator that does not have any more results"); } if (this.monadicEnumerator.Failed) { this.hasMoreResults = false; CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(this.monadicEnumerator.Exception); return(new ResponseMessage( statusCode: System.Net.HttpStatusCode.BadRequest, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, diagnostics: cosmosException.DiagnosticsContext)); } CrossPartitionReadFeedAsyncEnumerator enumerator = this.monadicEnumerator.Result; TryCatch <ReadFeedPage> monadicPage; try { if (!await enumerator.MoveNextAsync()) { throw new InvalidOperationException("Should not be calling enumerator that does not have any more results"); } monadicPage = enumerator.Current; } catch (Exception ex) { monadicPage = TryCatch <ReadFeedPage> .FromException(ex); } if (monadicPage.Failed) { CosmosException cosmosException = ExceptionToCosmosException.CreateFromException(monadicPage.Exception); if (!IsRetriableException(cosmosException)) { this.hasMoreResults = false; } return(new ResponseMessage( statusCode: cosmosException.StatusCode, requestMessage: null, headers: cosmosException.Headers, cosmosException: cosmosException, diagnostics: cosmosException.DiagnosticsContext)); } ReadFeedPage readFeedPage = monadicPage.Result; if (readFeedPage.State == default) { this.hasMoreResults = false; } // Make the continuation token match the older format: string continuationToken; if (readFeedPage.State != null) { List <CompositeContinuationToken> compositeContinuationTokens = new List <CompositeContinuationToken>(); CosmosArray compositeContinuationTokensCosmosArray = (CosmosArray)readFeedPage.State.ContinuationToken; foreach (CosmosElement arrayItem in compositeContinuationTokensCosmosArray) { ReadFeedContinuationToken readFeedContinuationToken = ReadFeedContinuationToken.MonadicConvertFromCosmosElement(arrayItem).Result; FeedRangeEpk feedRangeEpk = (FeedRangeEpk)readFeedContinuationToken.Range; ReadFeedState readFeedState = readFeedContinuationToken.State; CompositeContinuationToken compositeContinuationToken = new CompositeContinuationToken() { Range = feedRangeEpk.Range, Token = readFeedState.ContinuationToken.ToString(), }; compositeContinuationTokens.Add(compositeContinuationToken); } FeedRangeCompositeContinuation feedRangeCompositeContinuation = new FeedRangeCompositeContinuation( containerRid: string.Empty, feedRange: FeedRangeEpk.FullRange, compositeContinuationTokens); continuationToken = feedRangeCompositeContinuation.ToString(); } else { continuationToken = null; } return(new ResponseMessage( statusCode: System.Net.HttpStatusCode.OK, requestMessage: default,