public override TryCatch <CrossPartitionState <ChangeFeedState> > Visit(ChangeFeedStartFromContinuation startFromContinuation) { string continuationToken = startFromContinuation.Continuation; TryCatch <CosmosArray> monadicCosmosArray = CosmosArray.Monadic.Parse(continuationToken); if (monadicCosmosArray.Failed) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"Array expected for change feed continuation token: {continuationToken}.", innerException : monadicCosmosArray.Exception))); } CosmosArray cosmosArray = monadicCosmosArray.Result; if (cosmosArray.Count == 0) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"non empty array expected for change feed continuation token: {continuationToken}."))); } List <(FeedRangeInternal, ChangeFeedState)> rangeAndStates = new List <(FeedRangeInternal, ChangeFeedState)>(); foreach (CosmosElement arrayItem in cosmosArray) { TryCatch <ChangeFeedContinuationToken> monadicChangeFeedContinuationToken = ChangeFeedContinuationToken.MonadicConvertFromCosmosElement(arrayItem); if (monadicChangeFeedContinuationToken.Failed) { return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromException( new MalformedChangeFeedContinuationTokenException( message : $"Failed to parse change feed continuation token: {continuationToken}.", innerException : monadicChangeFeedContinuationToken.Exception))); } ChangeFeedContinuationToken changeFeedContinuationToken = monadicChangeFeedContinuationToken.Result; rangeAndStates.Add((changeFeedContinuationToken.Range, changeFeedContinuationToken.State)); } CrossPartitionState <ChangeFeedState> crossPartitionState = new CrossPartitionState <ChangeFeedState>(rangeAndStates); return(TryCatch <CrossPartitionState <ChangeFeedState> > .FromResult(crossPartitionState)); }
public ChangeFeedIteratorCore( IDocumentContainer documentContainer, ChangeFeedRequestOptions changeFeedRequestOptions, ChangeFeedStartFrom changeFeedStartFrom) { if (changeFeedStartFrom == null) { throw new ArgumentNullException(nameof(changeFeedStartFrom)); } this.documentContainer = documentContainer ?? throw new ArgumentNullException(nameof(documentContainer)); this.changeFeedRequestOptions = changeFeedRequestOptions ?? new ChangeFeedRequestOptions(); this.lazyMonadicEnumerator = new AsyncLazy <TryCatch <CrossPartitionChangeFeedAsyncEnumerator> >( valueFactory: async(cancellationToken) => { if (changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation) { TryCatch <CosmosElement> monadicParsedToken = CosmosElement.Monadic.Parse(startFromContinuation.Continuation); if (monadicParsedToken.Failed) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse continuation token: {startFromContinuation.Continuation}.", innerException: monadicParsedToken.Exception))); } TryCatch <VersionedAndRidCheckedCompositeToken> monadicVersionedToken = VersionedAndRidCheckedCompositeToken .MonadicCreateFromCosmosElement(monadicParsedToken.Result); if (monadicVersionedToken.Failed) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse continuation token: {startFromContinuation.Continuation}.", innerException: monadicVersionedToken.Exception))); } VersionedAndRidCheckedCompositeToken versionedAndRidCheckedCompositeToken = monadicVersionedToken.Result; if (versionedAndRidCheckedCompositeToken.VersionNumber == VersionedAndRidCheckedCompositeToken.Version.V1) { // Need to migrate continuation token if (!(versionedAndRidCheckedCompositeToken.ContinuationToken is CosmosArray cosmosArray)) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse get array continuation token: {startFromContinuation.Continuation}."))); } List <CosmosElement> changeFeedTokensV2 = new List <CosmosElement>(); foreach (CosmosElement arrayItem in cosmosArray) { if (!(arrayItem is CosmosObject cosmosObject)) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse get object in composite continuation: {startFromContinuation.Continuation}."))); } if (!cosmosObject.TryGetValue("min", out CosmosString min)) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse start of range: {cosmosObject}."))); } if (!cosmosObject.TryGetValue("max", out CosmosString max)) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse end of range: {cosmosObject}."))); } if (!cosmosObject.TryGetValue("token", out CosmosElement token)) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Failed to parse token: {cosmosObject}."))); } FeedRangeEpk feedRangeEpk = new FeedRangeEpk(new Documents.Routing.Range <string>( min: min.Value, max: max.Value, isMinInclusive: true, isMaxInclusive: false)); ChangeFeedState state = token is CosmosNull ? ChangeFeedState.Beginning() : ChangeFeedStateContinuation.Continuation(token); ChangeFeedContinuationToken changeFeedContinuationToken = new ChangeFeedContinuationToken(feedRangeEpk, state); changeFeedTokensV2.Add(ChangeFeedContinuationToken.ToCosmosElement(changeFeedContinuationToken)); } CosmosArray changeFeedTokensArrayV2 = CosmosArray.Create(changeFeedTokensV2); versionedAndRidCheckedCompositeToken = new VersionedAndRidCheckedCompositeToken( VersionedAndRidCheckedCompositeToken.Version.V2, changeFeedTokensArrayV2, versionedAndRidCheckedCompositeToken.Rid); } if (versionedAndRidCheckedCompositeToken.VersionNumber != VersionedAndRidCheckedCompositeToken.Version.V2) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"Wrong version number: {versionedAndRidCheckedCompositeToken.VersionNumber}."))); } string collectionRid = await documentContainer.GetResourceIdentifierAsync(cancellationToken); if (versionedAndRidCheckedCompositeToken.Rid != collectionRid) { return(TryCatch <CrossPartitionChangeFeedAsyncEnumerator> .FromException( new MalformedChangeFeedContinuationTokenException( message: $"rids mismatched. Expected: {collectionRid} but got {versionedAndRidCheckedCompositeToken.Rid}."))); } changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(versionedAndRidCheckedCompositeToken.ContinuationToken.ToString()); } TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate( documentContainer, changeFeedRequestOptions, changeFeedStartFrom, cancellationToken: default); return(monadicEnumerator); }); this.hasMoreResults = true; }
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())); } ChangeFeedPage changeFeedPage = enumerator.Current.Result; ResponseMessage responseMessage; if (changeFeedPage is ChangeFeedSuccessPage changeFeedSuccessPage) { responseMessage = new ResponseMessage(statusCode: System.Net.HttpStatusCode.OK) { Content = changeFeedSuccessPage.Content }; } else { responseMessage = new ResponseMessage(statusCode: System.Net.HttpStatusCode.NotModified); } CosmosElement innerContinuationToken = ((ChangeFeedStateContinuation)changeFeedPage.State).ContinuationToken; string continuationToken; if (this.changeFeedRequestOptions.EmitOldContinuationToken) { List <ChangeFeedContinuationToken> parsedChangeFeedTokens = new List <ChangeFeedContinuationToken>(); CosmosArray changeFeedTokens = (CosmosArray)innerContinuationToken; foreach (CosmosElement changeFeedToken in changeFeedTokens) { parsedChangeFeedTokens.Add(ChangeFeedContinuationToken.MonadicConvertFromCosmosElement(changeFeedToken).Result); } List <CompositeContinuationToken> compositeContinuationTokens = new List <CompositeContinuationToken>(); foreach (ChangeFeedContinuationToken changeFeedContinuationToken in parsedChangeFeedTokens) { string token = changeFeedContinuationToken.State is ChangeFeedStateContinuation changeFeedStateContinuation ? ((CosmosString)changeFeedStateContinuation.ContinuationToken).Value : null; Documents.Routing.Range <string> range = ((FeedRangeEpk)changeFeedContinuationToken.Range).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 { continuationToken = VersionedAndRidCheckedCompositeToken.ToCosmosElement( new VersionedAndRidCheckedCompositeToken( VersionedAndRidCheckedCompositeToken.Version.V2, innerContinuationToken, await this.documentContainer.GetResourceIdentifierAsync(cancellationToken))).ToString(); } responseMessage.Headers.ContinuationToken = continuationToken; responseMessage.Headers.RequestCharge = changeFeedPage.RequestCharge; responseMessage.Headers.ActivityId = changeFeedPage.ActivityId; return(responseMessage); }