Пример #1
0
        public async Task StartFromTimeAsync(bool useContinuations)
        {
            int numItems = 100;
            IDocumentContainer documentContainer = await CreateDocumentContainerAsync(numItems);

            TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                documentContainer,
                new ChangeFeedRequestOptions(),
                ChangeFeedStartFrom.Time(DateTime.UtcNow),
                cancellationToken: default);

            for (int i = 0; i < numItems; i++)
            {
                // Insert an item
                CosmosObject item = CosmosObject.Parse($"{{\"pk\" : {i} }}");
                while (true)
                {
                    TryCatch <Record> monadicCreateRecord = await documentContainer.MonadicCreateItemAsync(item, cancellationToken : default);

                    if (monadicCreateRecord.Succeeded)
                    {
                        break;
                    }
                }
            }

            Assert.IsTrue(monadicEnumerator.Succeeded);
            CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result;

            int globalCount = await(useContinuations
                ? DrainWithUntilNotModifiedWithContinuationTokens(documentContainer, enumerator)
                : DrainUntilNotModifedAsync(enumerator));

            Assert.AreEqual(numItems, globalCount);
        }
Пример #2
0
        private static async Task <int> DrainWithUntilNotModifiedWithContinuationTokens(
            IDocumentContainer documentContainer,
            CrossPartitionChangeFeedAsyncEnumerator enumerator)
        {
            List <CosmosElement> globalChanges = new List <CosmosElement>();

            while (true)
            {
                if (!await enumerator.MoveNextAsync())
                {
                    throw new InvalidOperationException();
                }

                Assert.IsTrue(enumerator.Current.Succeeded);

                if (!(enumerator.Current.Result is ChangeFeedSuccessPage changeFeedSuccessPage))
                {
                    break;
                }

                CosmosArray changes = GetChanges(changeFeedSuccessPage.Content);
                globalChanges.AddRange(changes);

                CosmosElement continuationToken = ((ChangeFeedStateContinuation)enumerator.Current.Result.State).ContinuationToken;

                enumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                    documentContainer,
                    new ChangeFeedRequestOptions(),
                    ChangeFeedStartFrom.ContinuationToken(continuationToken.ToString()),
                    cancellationToken: default).Result;
            }

            return(globalChanges.Count);
        }
Пример #3
0
        public async Task NoChangesAsync()
        {
            IDocumentContainer documentContainer = await CreateDocumentContainerAsync(numItems : 0);

            TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                documentContainer,
                new ChangeFeedRequestOptions(),
                ChangeFeedStartFrom.Beginning(),
                cancellationToken: default);

            Assert.IsTrue(monadicEnumerator.Succeeded);
            CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result;

            Assert.IsTrue(await enumerator.MoveNextAsync());
            Assert.IsTrue(enumerator.Current.Succeeded);
            Assert.IsTrue(enumerator.Current.Result is ChangeFeedNotModifiedPage);
            Assert.IsNotNull(enumerator.Current.Result.State);
        }
Пример #4
0
        public async Task StartFromBeginningAsync(bool useContinuations)
        {
            int numItems = 100;
            IDocumentContainer documentContainer = await CreateDocumentContainerAsync(numItems);

            TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                documentContainer,
                new ChangeFeedRequestOptions(),
                ChangeFeedStartFrom.Beginning(),
                cancellationToken: default);

            Assert.IsTrue(monadicEnumerator.Succeeded);
            CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result;

            int globalCount = await(useContinuations
                ? DrainWithUntilNotModifiedWithContinuationTokens(documentContainer, enumerator)
                : DrainUntilNotModifedAsync(enumerator));

            Assert.AreEqual(numItems, globalCount);
        }
Пример #5
0
        public async Task SomeChangesAsync()
        {
            IDocumentContainer documentContainer = await CreateDocumentContainerAsync(numItems : 1);

            TryCatch <CrossPartitionChangeFeedAsyncEnumerator> monadicEnumerator = CrossPartitionChangeFeedAsyncEnumerator.MonadicCreate(
                documentContainer,
                new ChangeFeedRequestOptions(),
                ChangeFeedStartFrom.Beginning(),
                cancellationToken: default);

            Assert.IsTrue(monadicEnumerator.Succeeded);
            CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result;

            // First page should be true and skip the 304 not modified
            Assert.IsTrue(await enumerator.MoveNextAsync());
            Assert.IsTrue(enumerator.Current.Succeeded);
            Assert.IsTrue(enumerator.Current.Result is ChangeFeedSuccessPage);

            // Second page should surface up the 304
            Assert.IsTrue(await enumerator.MoveNextAsync());
            Assert.IsTrue(enumerator.Current.Succeeded);
            Assert.IsTrue(enumerator.Current.Result is ChangeFeedNotModifiedPage);
        }
Пример #6
0
        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;
        }