From 0d924ee9813b128c63b0ce4cdab46ec04cf96a9d Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Wed, 8 Jul 2020 14:21:18 -0700 Subject: [PATCH 01/19] Revert "Revert "ChangeFeedRequestOptions Refactor (#1332)" (#1684)" This reverts commit b873a89316c34d667aca7123e8ed320871c7b445. --- .../PartitionSupervisorFactoryCore.cs | 15 +- .../Utils/ResultSetIteratorUtils.cs | 26 +- .../FeedRangeCompositeContinuation.cs | 28 +- .../src/FeedRange/FeedRangeContinuation.cs | 8 +- ...tinuationRequestMessagePopulatorVisitor.cs | 35 +++ .../src/FeedRange/FeedRanges/FeedRangeEPK.cs | 37 +-- .../FeedRange/FeedRanges/FeedRangeInternal.cs | 4 +- .../FeedRanges/FeedRangePartitionKey.cs | 15 +- .../FeedRanges/FeedRangePartitionKeyRange.cs | 13 +- .../FeedRangePartitionKeyRangeExtractor.cs | 53 ++++ ...eedRangeRequestMessagePopulatorVisitor.cs} | 24 +- .../IFeedRangeAsyncVisitor{TResult}.cs | 18 ++ .../IFeedRangeContinuationVisitor.cs | 15 + .../FeedRange/FeedRanges/IFeedRangeVisitor.cs | 15 + .../ChangeFeedRequestOptions.cs | 296 +++++++++++++++--- .../src/RequestOptions/QueryRequestOptions.cs | 18 +- .../src/Resource/Container/Container.cs | 90 ++---- .../Resource/Container/ContainerCore.Items.cs | 6 +- .../src/Resource/Container/ContainerCore.cs | 51 +-- .../Resource/Container/ContainerInlineCore.cs | 22 +- .../Resource/Container/ContainerInternal.cs | 18 +- .../src/Resource/Database/DatabaseCore.cs | 2 +- .../FeedIterators/FeedRangeIteratorCore.cs | 11 +- .../QueryResponses/ChangeFeedIteratorCore.cs | 183 +++++------ ...geFeedPartitionKeyResultSetIteratorCore.cs | 93 ++---- .../QueryResponses/StandByFeedIteratorCore.cs | 65 ++-- .../ChangeFeed/EstimatorTests.cs | 36 ++- .../ChangeFeed/SmokeTests.cs | 6 +- .../Contracts/ContractTests.cs | 37 ++- .../CosmosDiagnosticsTests.cs | 2 +- .../CosmosItemChangeFeedTests.cs | 131 +++++--- .../CosmosThroughputTests.cs | 2 +- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 123 ++++++-- .../LinqAggregateFunctionsBaselineTests.cs | 10 +- .../Utils/DiagnosticValidators.cs | 12 +- .../Query/EndToEnd.cs | 2 +- .../ChangeFeedProcessorCoreTests.cs | 10 +- .../ChangeFeedResultSetIteratorTests.cs | 34 +- .../Contracts/DotNetPreviewSDKAPI.json | 244 +++++++++++++-- .../FeedRange/ChangeFeedIteratorCoreTests.cs | 70 ++--- .../FeedRange/FeedRangeContinuationTests.cs | 35 --- .../FeedRange/FeedRangeTests.cs | 10 +- .../ReadFeedTokenIteratorCoreTests.cs | 42 +-- .../Query/FeedOptionTests.cs | 2 +- .../StandByFeedContinuationTokenTests.cs | 60 ++-- 45 files changed, 1284 insertions(+), 745 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeContinuationRequestMessagePopulatorVisitor.cs create mode 100644 Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRangeExtractor.cs rename Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/{FeedRangeVisitor.cs => FeedRangeRequestMessagePopulatorVisitor.cs} (60%) create mode 100644 Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeAsyncVisitor{TResult}.cs create mode 100644 Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeContinuationVisitor.cs create mode 100644 Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeVisitor.cs diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionSupervisorFactoryCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionSupervisorFactoryCore.cs index 7c1fa51973..7e8f02fac3 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionSupervisorFactoryCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionSupervisorFactoryCore.cs @@ -22,21 +22,18 @@ public PartitionSupervisorFactoryCore( FeedProcessorFactory partitionProcessorFactory, ChangeFeedLeaseOptions options) { - if (observerFactory == null) throw new ArgumentNullException(nameof(observerFactory)); - if (leaseManager == null) throw new ArgumentNullException(nameof(leaseManager)); - if (options == null) throw new ArgumentNullException(nameof(options)); - if (partitionProcessorFactory == null) throw new ArgumentNullException(nameof(partitionProcessorFactory)); - - this.observerFactory = observerFactory; - this.leaseManager = leaseManager; - this.changeFeedLeaseOptions = options; - this.partitionProcessorFactory = partitionProcessorFactory; + this.observerFactory = observerFactory ?? throw new ArgumentNullException(nameof(observerFactory)); + this.leaseManager = leaseManager ?? throw new ArgumentNullException(nameof(leaseManager)); + this.changeFeedLeaseOptions = options ?? throw new ArgumentNullException(nameof(options)); + this.partitionProcessorFactory = partitionProcessorFactory ?? throw new ArgumentNullException(nameof(partitionProcessorFactory)); } public override PartitionSupervisor Create(DocumentServiceLease lease) { if (lease == null) + { throw new ArgumentNullException(nameof(lease)); + } ChangeFeedObserver changeFeedObserver = this.observerFactory.CreateObserver(); FeedProcessor processor = this.partitionProcessorFactory.Create(lease, changeFeedObserver); diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs index dca59d5286..0d80835da2 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs @@ -16,20 +16,32 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore BuildResultSetIterator DateTime? startTime, bool startFromBeginning) { - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions(); - if (startTime.HasValue) + ChangeFeedRequestOptions.StartFrom startFrom; + if (continuationToken != null) { - requestOptions.StartTime = startTime.Value; + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuationToken); + } + else if (startTime.HasValue) + { + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromTime(startTime.Value); } else if (startFromBeginning) { - requestOptions.StartTime = ChangeFeedRequestOptions.DateTimeStartFromBeginning; + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); + } + else + { + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromNow(); } + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + MaxItemCount = maxItemCount, + FeedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId), + From = startFrom, + }; + return new ChangeFeedPartitionKeyResultSetIteratorCore( - partitionKeyRangeId: partitionKeyRangeId, - continuationToken: continuationToken, - maxItemCount: maxItemCount, clientContext: container.ClientContext, container: container, options: requestOptions); diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/Continuations/FeedRangeCompositeContinuation.cs b/Microsoft.Azure.Cosmos/src/FeedRange/Continuations/FeedRangeCompositeContinuation.cs index 5035a6f388..8983c27350 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/Continuations/FeedRangeCompositeContinuation.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/Continuations/FeedRangeCompositeContinuation.cs @@ -35,13 +35,6 @@ private FeedRangeCompositeContinuation( this.CompositeContinuationTokens = new Queue(); } - public override void Accept( - FeedRangeVisitor visitor, - Action fillContinuation) - { - visitor.Visit(this, fillContinuation); - } - public FeedRangeCompositeContinuation( string containerRid, FeedRangeInternal feedRange, @@ -100,6 +93,21 @@ public FeedRangeCompositeContinuation( public override string GetContinuation() => this.CurrentToken?.Token; + public override FeedRange GetFeedRange() + { + if (this.FeedRange is FeedRangePartitionKeyRange) + { + return this.FeedRange; + } + + if (this.CurrentToken != null) + { + return new FeedRangeEPK(this.CurrentToken.Range); + } + + return null; + } + public override string ToString() { return JsonConvert.SerializeObject(this); @@ -228,7 +236,7 @@ private static bool TryParseAsCompositeContinuationToken( { return false; } - } + } private static CompositeContinuationToken CreateCompositeContinuationTokenForRange( string minInclusive, @@ -252,7 +260,7 @@ private void MoveToNextToken() // 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; } @@ -316,5 +324,7 @@ private void CreateChildRanges(IReadOnlyList keyRan return keyRanges; } + + public override void Accept(IFeedRangeContinuationVisitor visitor) => visitor.Visit(this); } } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRangeContinuation.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRangeContinuation.cs index 09d2210755..b27f16838f 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRangeContinuation.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRangeContinuation.cs @@ -33,12 +33,10 @@ public FeedRangeContinuation( this.ContainerRid = containerRid; } - public abstract void Accept( - FeedRangeVisitor visitor, - Action fillContinuation); - public abstract string GetContinuation(); + public abstract FeedRange GetFeedRange(); + public abstract void ReplaceContinuation(string continuationToken); public abstract bool IsDone { get; } @@ -64,5 +62,7 @@ public static bool TryParse( ContainerInternal containerCore, ResponseMessage responseMessage, CancellationToken cancellationToken); + + public abstract void Accept(IFeedRangeContinuationVisitor visitor); } } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeContinuationRequestMessagePopulatorVisitor.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeContinuationRequestMessagePopulatorVisitor.cs new file mode 100644 index 0000000000..48ccfc5b89 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeContinuationRequestMessagePopulatorVisitor.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + + /// + /// Visitor to populate RequestMessage headers and properties based on FeedRange. + /// + internal sealed class FeedRangeContinuationRequestMessagePopulatorVisitor : IFeedRangeContinuationVisitor + { + private readonly RequestMessage request; + private readonly Action fillContinuation; + + public FeedRangeContinuationRequestMessagePopulatorVisitor(RequestMessage request, Action fillContinuation) + { + this.request = request ?? throw new ArgumentNullException(nameof(request)); + this.fillContinuation = fillContinuation ?? throw new ArgumentNullException(nameof(fillContinuation)); + } + + public void Visit(FeedRangeCompositeContinuation feedRangeCompositeContinuation) + { + // In case EPK has already been set by compute + if (!this.request.Properties.ContainsKey(HandlerConstants.StartEpkString)) + { + this.request.Properties[HandlerConstants.StartEpkString] = feedRangeCompositeContinuation.CurrentToken.Range.Min; + this.request.Properties[HandlerConstants.EndEpkString] = feedRangeCompositeContinuation.CurrentToken.Range.Max; + } + + this.fillContinuation(this.request, feedRangeCompositeContinuation.GetContinuation()); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEPK.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEPK.cs index 32a4467493..e0bd082cf2 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEPK.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEPK.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { + using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -15,29 +16,28 @@ namespace Microsoft.Azure.Cosmos /// internal sealed class FeedRangeEPK : FeedRangeInternal { - public Documents.Routing.Range Range { get; } - - public static FeedRangeEPK ForFullRange() - { - return new FeedRangeEPK(new Documents.Routing.Range( - Documents.Routing.PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, - Documents.Routing.PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, - isMinInclusive: true, - isMaxInclusive: false)); - } + public static readonly FeedRangeEPK FullRange = new FeedRangeEPK(new Documents.Routing.Range( + Documents.Routing.PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, + Documents.Routing.PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, + isMinInclusive: true, + isMaxInclusive: false)); public FeedRangeEPK(Documents.Routing.Range range) { + if (range == null) + { + throw new ArgumentNullException(nameof(range)); + } + this.Range = range; } + public Documents.Routing.Range Range { get; } + public override Task>> GetEffectiveRangesAsync( IRoutingMapProvider routingMapProvider, string containerRid, - Documents.PartitionKeyDefinition partitionKeyDefinition) - { - return Task.FromResult(new List>() { this.Range }); - } + Documents.PartitionKeyDefinition partitionKeyDefinition) => Task.FromResult(new List>() { this.Range }); public override async Task> GetPartitionKeyRangesAsync( IRoutingMapProvider routingMapProvider, @@ -49,10 +49,11 @@ public override async Task> GetPartitionKeyRangesAsync( return partitionKeyRanges.Select(partitionKeyRange => partitionKeyRange.Id); } - public override void Accept(FeedRangeVisitor visitor) - { - visitor.Visit(this); - } + public override void Accept(IFeedRangeVisitor visitor) => visitor.Visit(this); + + public override Task AcceptAsync( + IFeedRangeAsyncVisitor visitor, + CancellationToken cancellationToken = default) => visitor.VisitAsync(this, cancellationToken); public override string ToString() => this.Range.ToString(); } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs index 6fe217546f..b38f3265e1 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs @@ -25,7 +25,9 @@ public abstract Task> GetPartitionKeyRangesAsync( Documents.PartitionKeyDefinition partitionKeyDefinition, CancellationToken cancellationToken); - public abstract void Accept(FeedRangeVisitor visitor); + public abstract void Accept(IFeedRangeVisitor visitor); + + public abstract Task AcceptAsync(IFeedRangeAsyncVisitor visitor, CancellationToken cancellationToken = default); public abstract override string ToString(); diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKey.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKey.cs index 26d4228cf8..000b343bcc 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKey.cs @@ -24,14 +24,12 @@ public FeedRangePartitionKey(PartitionKey partitionKey) public override Task>> GetEffectiveRangesAsync( IRoutingMapProvider routingMapProvider, string containerRid, - Documents.PartitionKeyDefinition partitionKeyDefinition) - { - return Task.FromResult(new List> + Documents.PartitionKeyDefinition partitionKeyDefinition) => Task.FromResult( + new List> { Documents.Routing.Range.GetPointRange( this.PartitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)) }); - } public override async Task> GetPartitionKeyRangesAsync( IRoutingMapProvider routingMapProvider, @@ -44,10 +42,11 @@ public override async Task> GetPartitionKeyRangesAsync( return new List() { range.Id }; } - public override void Accept(FeedRangeVisitor visitor) - { - visitor.Visit(this); - } + public override void Accept(IFeedRangeVisitor visitor) => visitor.Visit(this); + + public override Task AcceptAsync( + IFeedRangeAsyncVisitor visitor, + CancellationToken cancellationToken = default) => visitor.VisitAsync(this, cancellationToken); public override string ToString() => this.PartitionKey.InternalKey.ToJsonString(); } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRange.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRange.cs index 984829ffae..c6c0b50cd9 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRange.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRange.cs @@ -19,13 +19,13 @@ namespace Microsoft.Azure.Cosmos /// internal sealed class FeedRangePartitionKeyRange : FeedRangeInternal { - public string PartitionKeyRangeId { get; } - public FeedRangePartitionKeyRange(string partitionKeyRangeId) { this.PartitionKeyRangeId = partitionKeyRangeId; } + public string PartitionKeyRangeId { get; } + public override async Task>> GetEffectiveRangesAsync( IRoutingMapProvider routingMapProvider, string containerRid, @@ -74,10 +74,11 @@ public override Task> GetPartitionKeyRangesAsync( return Task.FromResult(partitionKeyRanges); } - public override void Accept(FeedRangeVisitor visitor) - { - visitor.Visit(this); - } + public override void Accept(IFeedRangeVisitor visitor) => visitor.Visit(this); + + public override Task AcceptAsync( + IFeedRangeAsyncVisitor visitor, + CancellationToken cancellationToken = default) => visitor.VisitAsync(this, cancellationToken); public override string ToString() => this.PartitionKeyRangeId; } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRangeExtractor.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRangeExtractor.cs new file mode 100644 index 0000000000..2110c5410c --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangePartitionKeyRangeExtractor.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Documents; + + internal sealed class FeedRangePartitionKeyRangeExtractor : IFeedRangeAsyncVisitor>> + { + private readonly ContainerInternal container; + + public FeedRangePartitionKeyRangeExtractor(ContainerInternal container) + { + this.container = container ?? throw new ArgumentNullException(nameof(container)); + } + + public async Task>> VisitAsync(FeedRangePartitionKey feedRange, CancellationToken cancellationToken = default) + { + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.container.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); + PartitionKeyDefinition partitionKeyDefinition = await this.container.GetPartitionKeyDefinitionAsync(cancellationToken); + return await feedRange.GetEffectiveRangesAsync( + partitionKeyRangeCache, + await this.container.GetRIDAsync(cancellationToken), + partitionKeyDefinition); + } + + public async Task>> VisitAsync(FeedRangePartitionKeyRange feedRange, CancellationToken cancellationToken = default) + { + // Migration from PKRangeId scenario + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.container.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); + return await feedRange.GetEffectiveRangesAsync( + routingMapProvider: partitionKeyRangeCache, + containerRid: await this.container.GetRIDAsync(cancellationToken), + partitionKeyDefinition: null); + } + + public async Task>> VisitAsync(FeedRangeEPK feedRange, CancellationToken cancellationToken = default) + { + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.container.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); + IReadOnlyList pkRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: await this.container.GetRIDAsync(cancellationToken), + range: feedRange.Range, + forceRefresh: false); + return pkRanges.Select(pkRange => pkRange.ToRange()).ToList(); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeVisitor.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeRequestMessagePopulatorVisitor.cs similarity index 60% rename from Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeVisitor.cs rename to Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeRequestMessagePopulatorVisitor.cs index 1910aa5d02..118bbdcb44 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeVisitor.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeRequestMessagePopulatorVisitor.cs @@ -7,14 +7,15 @@ namespace Microsoft.Azure.Cosmos using System; /// - /// Visitor to populate RequestMessage headers and properties based on FeedRange and Continuation. + /// Visitor to populate RequestMessage headers and properties based on FeedRange. /// - internal sealed class FeedRangeVisitor + internal sealed class FeedRangeRequestMessagePopulatorVisitor : IFeedRangeVisitor { private readonly RequestMessage request; - public FeedRangeVisitor(RequestMessage request) + + public FeedRangeRequestMessagePopulatorVisitor(RequestMessage request) { - this.request = request; + this.request = request ?? throw new ArgumentNullException(nameof(request)); } public void Visit(FeedRangePartitionKey feedRange) @@ -24,26 +25,17 @@ public void Visit(FeedRangePartitionKey feedRange) public void Visit(FeedRangePartitionKeyRange feedRange) { - ChangeFeedRequestOptions.FillPartitionKeyRangeId(this.request, feedRange.PartitionKeyRangeId); + this.request.PartitionKeyRangeId = new Documents.PartitionKeyRangeIdentity(feedRange.PartitionKeyRangeId); } public void Visit(FeedRangeEPK feedRange) - { - // No-op since the range is defined by the composite continuation token - } - - public void Visit( - FeedRangeCompositeContinuation continuation, - Action fillContinuation) { // In case EPK has already been set by compute if (!this.request.Properties.ContainsKey(HandlerConstants.StartEpkString)) { - this.request.Properties[HandlerConstants.StartEpkString] = continuation.CurrentToken.Range.Min; - this.request.Properties[HandlerConstants.EndEpkString] = continuation.CurrentToken.Range.Max; + this.request.Properties[HandlerConstants.StartEpkString] = feedRange.Range.Min; + this.request.Properties[HandlerConstants.EndEpkString] = feedRange.Range.Max; } - - fillContinuation(this.request, continuation.GetContinuation()); } } } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeAsyncVisitor{TResult}.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeAsyncVisitor{TResult}.cs new file mode 100644 index 0000000000..1be67b18b6 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeAsyncVisitor{TResult}.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System.Threading; + using System.Threading.Tasks; + + internal interface IFeedRangeAsyncVisitor + { + public abstract Task VisitAsync(FeedRangePartitionKey feedRange, CancellationToken cancellationToken = default); + + public abstract Task VisitAsync(FeedRangePartitionKeyRange feedRange, CancellationToken cancellationToken = default); + + public abstract Task VisitAsync(FeedRangeEPK feedRange, CancellationToken cancellationToken = default); + } +} diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeContinuationVisitor.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeContinuationVisitor.cs new file mode 100644 index 0000000000..93fcceb098 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeContinuationVisitor.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Text; + + internal interface IFeedRangeContinuationVisitor + { + public void Visit(FeedRangeCompositeContinuation feedRangeCompositeContinuation); + } +} diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeVisitor.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeVisitor.cs new file mode 100644 index 0000000000..9b02321702 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/IFeedRangeVisitor.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + internal interface IFeedRangeVisitor + { + public abstract void Visit(FeedRangePartitionKey feedRange); + + public abstract void Visit(FeedRangePartitionKeyRange feedRange); + + public abstract void Visit(FeedRangeEPK feedRange); + } +} diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index a5c514c928..038e4dec74 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -17,10 +17,9 @@ namespace Microsoft.Azure.Cosmos #else internal #endif - class ChangeFeedRequestOptions : RequestOptions + sealed class ChangeFeedRequestOptions : RequestOptions { - internal const string IfNoneMatchAllHeaderValue = "*"; - internal static readonly DateTime DateTimeStartFromBeginning = DateTime.MinValue.ToUniversalTime(); + private int? maxItemCount; /// /// Gets or sets the maximum number of items to be returned in the enumeration operation in the Azure Cosmos DB service. @@ -28,16 +27,32 @@ class ChangeFeedRequestOptions : RequestOptions /// /// The maximum number of items to be returned in the enumeration operation. /// - public int? MaxItemCount { get; set; } + public int? MaxItemCount + { + get => this.maxItemCount; + set + { + if (value.HasValue && (value.Value <= 0)) + { + throw new ArgumentOutOfRangeException($"{nameof(this.MaxItemCount)} must be a positive value."); + } + + this.maxItemCount = value; + } + } /// - /// Gets or sets a particular point in time to start to read the change feed. + /// Gets or sets where the ChangeFeed operation should start from. If not set then the ChangeFeed operation will start from now. /// /// /// Only applies in the case where no FeedToken is provided or the FeedToken was never used in a previous iterator. - /// In order to read the Change Feed from the beginning, set this to DateTime.MinValue.ToUniversalTime(). /// - public DateTime? StartTime { get; set; } + public StartFrom From { get; set; } = StartFromNow.Singleton; + + /// + /// Gets or set which ranges to execute the ChangeFeed operation on. + /// + public FeedRange FeedRange { get; set; } = FeedRangeEPK.FullRange; /// /// Fill the CosmosRequestMessage headers with the set properties @@ -45,62 +60,273 @@ class ChangeFeedRequestOptions : RequestOptions /// The internal override void PopulateRequestOptions(RequestMessage request) { - // Check if no Continuation Token is present - if (string.IsNullOrEmpty(request.Headers.IfNoneMatch)) + Debug.Assert(request != null); + + base.PopulateRequestOptions(request); + + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + if (this.From == null) { - if (this.StartTime == null) - { - request.Headers.IfNoneMatch = ChangeFeedRequestOptions.IfNoneMatchAllHeaderValue; - } - else if (this.StartTime != null - && this.StartTime != ChangeFeedRequestOptions.DateTimeStartFromBeginning) + throw new InvalidOperationException($"{nameof(ChangeFeedRequestOptions)}.{nameof(ChangeFeedRequestOptions.StartFrom)} needs to be set to a value."); + } + + this.From.Accept(visitor); + + if (this.MaxItemCount.HasValue) + { + request.Headers.Add( + HttpConstants.HttpHeaders.PageSize, + this.MaxItemCount.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (this.FeedRange != null) + { + FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(request); + ((FeedRangeInternal)this.FeedRange).Accept(feedRangeVisitor); + } + + request.Headers.Add( + HttpConstants.HttpHeaders.A_IM, + HttpConstants.A_IMHeaderValues.IncrementalFeed); + } + + /// + /// IfMatchEtag is inherited from the base class but not used. + /// + [Obsolete("IfMatchEtag is inherited from the base class but not used.")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public new string IfMatchEtag + { + get => throw new NotSupportedException($"{nameof(ChangeFeedRequestOptions)} does not use the {nameof(this.IfMatchEtag)} property."); + set => throw new NotSupportedException($"{nameof(ChangeFeedRequestOptions)} does not use the {nameof(this.IfMatchEtag)} property."); + } + + /// + /// IfNoneMatchEtag is inherited from the base class but not used. + /// + [Obsolete("IfNoneMatchEtag is inherited from the base class but not used.")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public new string IfNoneMatchEtag + { + get => throw new NotSupportedException($"{nameof(ChangeFeedRequestOptions)} does not use the {nameof(this.IfNoneMatchEtag)} property."); + set => throw new NotSupportedException($"{nameof(ChangeFeedRequestOptions)} does not use the {nameof(this.IfNoneMatchEtag)} property."); + } + + /// + /// Base class for where to start a ChangeFeed operation in . + /// + /// Use one of the static constructors to generate a StartFrom option. + public abstract class StartFrom + { + /// + /// Initializes an instance of the class. + /// + protected StartFrom() + { + } + + internal abstract void Accept(StartFromVisitor visitor); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static StartFrom CreateFromNow() + { + return StartFromNow.Singleton; + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time to start reading from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static StartFrom CreateFromTime(DateTime dateTime) + { + return new StartFromTime(dateTime); + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. + /// + /// The continuation to resume from. + /// A that tells the ChangeFeed operation to start reading changes from a save point. + public static StartFrom CreateFromContinuation(string continuation) + { + return new StartFromContinuation(continuation); + } + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static StartFrom CreateFromBeginning() + { + return StartFromBeginning.Singleton; + } + } + + internal abstract class StartFromVisitor + { + public abstract void Visit(StartFromNow startFromNow); + public abstract void Visit(StartFromTime startFromTime); + public abstract void Visit(StartFromContinuation startFromContinuation); + public abstract void Visit(StartFromBeginning startFromBeginning); + } + + internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor + { + private const string IfNoneMatchAllHeaderValue = "*"; + private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); + + private readonly RequestMessage requestMessage; + + public PopulateStartFromRequestOptionVisitor(RequestMessage requestMessage) + { + this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); + } + + public override void Visit(StartFromNow startFromNow) + { + this.requestMessage.Headers.IfNoneMatch = PopulateStartFromRequestOptionVisitor.IfNoneMatchAllHeaderValue; + } + + public override void Visit(StartFromTime startFromTime) + { + // Our current public contract for ChangeFeedProcessor uses DateTime.MinValue.ToUniversalTime as beginning. + // We need to add a special case here, otherwise it would send it as normal StartTime. + // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, + // it would break multi master Change Feed Processor users using Start From Beginning semantics. + // It's also an optimization, since the backend won't have to binary search for the value. + if (startFromTime.Time != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) { - request.Headers.Add(HttpConstants.HttpHeaders.IfModifiedSince, this.StartTime.Value.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture)); + this.requestMessage.Headers.Add( + HttpConstants.HttpHeaders.IfModifiedSince, + startFromTime.Time.ToString("r", CultureInfo.InvariantCulture)); } } - ChangeFeedRequestOptions.FillMaxItemCount(request, this.MaxItemCount); - request.Headers.Add(HttpConstants.HttpHeaders.A_IM, HttpConstants.A_IMHeaderValues.IncrementalFeed); + public override void Visit(StartFromContinuation startFromContinuation) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + this.requestMessage.Headers.IfNoneMatch = startFromContinuation.Continuation; + } - base.PopulateRequestOptions(request); + public override void Visit(StartFromBeginning startFromBeginning) + { + // We don't need to set any headers to start from the beginning + } } - internal static void FillPartitionKeyRangeId(RequestMessage request, string partitionKeyRangeId) + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + internal sealed class StartFromNow : StartFrom { - Debug.Assert(request != null); + public static readonly StartFromNow Singleton = new StartFromNow(); + + /// + /// Intializes an instance of the class. + /// + public StartFromNow() + : base() + { + } - if (!string.IsNullOrEmpty(partitionKeyRangeId)) + internal override void Accept(StartFromVisitor visitor) { - request.PartitionKeyRangeId = new PartitionKeyRangeIdentity(partitionKeyRangeId); + visitor.Visit(this); } } - internal static void FillPartitionKey(RequestMessage request, PartitionKey partitionKey) + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + internal sealed class StartFromTime : StartFrom { - Debug.Assert(request != null); + /// + /// Initializes an instance of the class. + /// + /// The time to start reading from. + public StartFromTime(DateTime time) + : base() + { + if (time.Kind != DateTimeKind.Utc) + { + throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); + } - request.Headers.PartitionKey = partitionKey.ToJsonString(); + this.Time = time; + } + + /// + /// Gets the time the ChangeFeed operation should start reading from. + /// + public DateTime Time { get; } + + internal override void Accept(StartFromVisitor visitor) + { + visitor.Visit(this); + } } - internal static void FillContinuationToken(RequestMessage request, string continuationToken) + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. + /// + internal sealed class StartFromContinuation : StartFrom { - Debug.Assert(request != null); + /// + /// Initializes an instance of the class. + /// + /// The continuation to resume from. + public StartFromContinuation(string continuation) + : base() + { + if (string.IsNullOrWhiteSpace(continuation)) + { + throw new ArgumentOutOfRangeException($"{nameof(continuation)} must not be null, empty, or whitespace."); + } - if (!string.IsNullOrWhiteSpace(continuationToken)) + this.Continuation = continuation; + } + + /// + /// Gets the continuation to resume from. + /// + public string Continuation { get; } + + internal override void Accept(StartFromVisitor visitor) { - // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - request.Headers.IfNoneMatch = continuationToken; + visitor.Visit(this); } } - internal static void FillMaxItemCount(RequestMessage request, int? maxItemCount) + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from the beginning of time. + /// + internal sealed class StartFromBeginning : StartFrom { - Debug.Assert(request != null); + public static readonly StartFromBeginning Singleton = new StartFromBeginning(); - if (maxItemCount.HasValue) + public StartFromBeginning() + : base() { - request.Headers.Add(HttpConstants.HttpHeaders.PageSize, maxItemCount.Value.ToString(CultureInfo.InvariantCulture)); } + + internal override void Accept(StartFromVisitor visitor) + { + visitor.Visit(this); + } + } + + internal ChangeFeedRequestOptions Clone() + { + return new ChangeFeedRequestOptions() + { + MaxItemCount = this.maxItemCount, + From = this.From, + FeedRange = this.FeedRange, + }; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs index 5c58362766..2bc739ff77 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs @@ -159,6 +159,8 @@ public ConsistencyLevel? ConsistencyLevel internal TestInjections TestSettings { get; set; } + internal FeedRange FeedRange { get; set; } + /// /// Fill the CosmosRequestMessage headers with the set properties /// @@ -231,6 +233,12 @@ internal override void PopulateRequestOptions(RequestMessage request) request.Headers.Add(HttpConstants.HttpHeaders.PopulateQueryMetrics, bool.TrueString); + if (this.FeedRange != null) + { + FeedRangeRequestMessagePopulatorVisitor queryFeedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(request); + ((FeedRangeInternal)this.FeedRange).Accept(queryFeedRangeVisitor); + } + base.PopulateRequestOptions(request); } @@ -267,15 +275,5 @@ internal static void FillContinuationToken( request.Headers.ContinuationToken = continuationToken; } } - - internal static void FillMaxItemCount( - RequestMessage request, - int? maxItemCount) - { - if (maxItemCount != null && maxItemCount.HasValue) - { - request.Headers.Add(HttpConstants.HttpHeaders.PageSize, maxItemCount.Value.ToString(CultureInfo.InvariantCulture)); - } - } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index cd72b15467..20484d717d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1079,40 +1079,8 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken = default(CancellationToken)); /// - /// This method creates an iterator to consume the container's Change Feed. + /// This method creates an iterator to consume a Change Feed. /// - /// (Optional) The continuation from a previous Change Feed iterator. - /// (Optional) The options for the Change Feed consumption. - /// An iterator to go through the Change Feed. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// - /// - /// - public abstract FeedIterator GetChangeFeedStreamIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// This method creates an iterator to consume a FeedRange's Change Feed. - /// - /// A FeedRange obtained from or from a previous FeedIterator /// (Optional) The options for the Change Feed consumption. /// /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api @@ -1121,7 +1089,15 @@ public abstract FeedIterator GetChangeFeedStreamIterator( /// feedRanges = await this.Container.GetFeedRangesAsync(); /// // Distribute feedRanges across multiple compute units and pass each one to a different iterator - /// using (FeedIterator feedIterator = this.Container.GetChangeFeedStreamIterator(feedRanges[0])) + /// + /// ChangeFeedRequestOptions options = new ChangeFeedRequestOptions() + /// { + /// FeedRange = feedRanges[0] + /// } + /// + /// FeedIterator feedIterator = this.Container.GetChangeFeedStreamIterator(options); + /// + /// while (feedIterator.HasMoreResults) /// { /// while (feedIterator.HasMoreResults) /// { @@ -1138,9 +1114,8 @@ public abstract FeedIterator GetChangeFeedStreamIterator( /// ]]> /// /// - /// An iterator to go through the Change Feed for a particular FeedRange. + /// An iterator to go through the Change Feed. public abstract FeedIterator GetChangeFeedStreamIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null); /// @@ -1175,37 +1150,8 @@ public abstract FeedIterator GetChangeFeedStreamIterator( ChangeFeedRequestOptions changeFeedRequestOptions = null); /// - /// This method creates an iterator to consume the container's Change Feed. + /// This method creates an iterator to consume a Change Feed. /// - /// (Optional) The continuation from a previous Change Feed iterator. - /// (Optional) The options for the Change Feed consumption. - /// An iterator to go through the Change Feed. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// feedIterator = this.Container.GetChangeFeedIterator()) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// FeedResponse response = await feedIterator.ReadNextAsync(); - /// foreach (var item in response) - /// { - /// Console.WriteLine(item); - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetChangeFeedIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// This method creates an iterator to consume a FeedRange's Change Feed. - /// - /// A FeedRange obtained from . /// (Optional) The options for the Change Feed consumption. /// /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api @@ -1214,7 +1160,14 @@ public abstract FeedIterator GetChangeFeedIterator( /// feedRanges = await this.Container.GetFeedRangessAsync(); /// // Distribute feedRangess across multiple compute units and pass each one to a different iterator - /// using (FeedIterator feedIterator = this.Container.GetChangeFeedIterator(feedRanges[0])) + /// + /// ChangeFeedRequestOptions options = new ChangeFeedRequestOptions() + /// { + /// FeedRange = feedRanges[0] + /// } + /// + /// FeedIterator feedIterator = this.Container.GetChangeFeedIterator(options); + /// while (feedIterator.HasMoreResults) /// { /// while (feedIterator.HasMoreResults) /// { @@ -1228,9 +1181,8 @@ public abstract FeedIterator GetChangeFeedIterator( /// ]]> /// /// - /// An iterator to go through the Change Feed for a particular FeedRange. + /// An iterator to go through the Change Feed. public abstract FeedIterator GetChangeFeedIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null); /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 2281ab858a..e4d3269c90 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -543,17 +543,13 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti return allRanges.Select(e => StandByFeedContinuationToken.CreateForRange(containerRid, e.MinInclusive, e.MaxExclusive)); } - public override FeedIterator GetStandByFeedIterator( - string continuationToken = null, - int? maxItemCount = null, + internal override FeedIterator GetStandByFeedIterator( ChangeFeedRequestOptions requestOptions = null) { ChangeFeedRequestOptions cosmosQueryRequestOptions = requestOptions as ChangeFeedRequestOptions ?? new ChangeFeedRequestOptions(); return new StandByFeedIteratorCore( clientContext: this.ClientContext, - continuationToken: continuationToken, - maxItemCount: maxItemCount, container: this, options: cosmosQueryRequestOptions); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index d22753d2a6..21459f19af 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -241,7 +241,7 @@ public async Task> GetFeedRangesAsync( isMinInclusive: true, isMaxInclusive: false), forceRefresh: true); - List feedTokens = new List(partitionKeyRanges.Count); + List feedTokens = new List(partitionKeyRanges.Count); foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) { feedTokens.Add(new FeedRangeEPK(partitionKeyRange.ToRange())); @@ -251,76 +251,47 @@ public async Task> GetFeedRangesAsync( } public override FeedIterator GetChangeFeedStreamIterator( - string continuationToken = null, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return ChangeFeedIteratorCore.Create( + return new ChangeFeedIteratorCore( container: this, - feedRangeInternal: null, - continuation: continuationToken, changeFeedRequestOptions: changeFeedRequestOptions); } - public override FeedIterator GetChangeFeedStreamIterator( - FeedRange feedRange, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; - return ChangeFeedIteratorCore.Create( - container: this, - feedRangeInternal: feedRangeInternal, - continuation: null, - changeFeedRequestOptions: changeFeedRequestOptions); - } public override FeedIterator GetChangeFeedStreamIterator( PartitionKey partitionKey, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return ChangeFeedIteratorCore.Create( + changeFeedRequestOptions ??= new ChangeFeedRequestOptions(); + changeFeedRequestOptions.FeedRange = new FeedRangePartitionKey(partitionKey); + return new ChangeFeedIteratorCore( container: this, - feedRangeInternal: new FeedRangePartitionKey(partitionKey), - continuation: null, changeFeedRequestOptions: changeFeedRequestOptions); } public override FeedIterator GetChangeFeedIterator( - string continuationToken = null, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - ChangeFeedIteratorCore changeFeedIteratorCore = ChangeFeedIteratorCore.Create( + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( container: this, - feedRangeInternal: null, - continuation: continuationToken, changeFeedRequestOptions: changeFeedRequestOptions); return new FeedIteratorCore(changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); } - public override FeedIterator GetChangeFeedIterator( - FeedRange feedRange, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; - ChangeFeedIteratorCore changeFeedIteratorCore = ChangeFeedIteratorCore.Create( - container: this, - feedRangeInternal: feedRangeInternal, - continuation: null, - changeFeedRequestOptions: changeFeedRequestOptions); - - return new FeedIteratorCore(changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); - } public override FeedIterator GetChangeFeedIterator( PartitionKey partitionKey, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - ChangeFeedIteratorCore changeFeedIteratorCore = ChangeFeedIteratorCore.Create( + changeFeedRequestOptions ??= new ChangeFeedRequestOptions(); + changeFeedRequestOptions.FeedRange = new FeedRangePartitionKey(partitionKey); + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( container: this, - feedRangeInternal: new FeedRangePartitionKey(partitionKey), - continuation: null, changeFeedRequestOptions: changeFeedRequestOptions); return new FeedIteratorCore(changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); } + public override async Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default(CancellationToken)) @@ -483,4 +454,4 @@ private Task ProcessResourceOperationStreamAsync( cancellationToken: cancellationToken); } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 841c100a0f..a73f8dfa2a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -347,17 +347,9 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti } public override FeedIterator GetChangeFeedStreamIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return base.GetChangeFeedStreamIterator(continuationToken, changeFeedRequestOptions); - } - - public override FeedIterator GetChangeFeedStreamIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return base.GetChangeFeedStreamIterator(feedRange, changeFeedRequestOptions); + return base.GetChangeFeedStreamIterator(changeFeedRequestOptions); } public override FeedIterator GetChangeFeedStreamIterator( @@ -368,17 +360,9 @@ public override FeedIterator GetChangeFeedStreamIterator( } public override FeedIterator GetChangeFeedIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return base.GetChangeFeedIterator(continuationToken, changeFeedRequestOptions); - } - - public override FeedIterator GetChangeFeedIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return base.GetChangeFeedIterator(feedRange, changeFeedRequestOptions); + return base.GetChangeFeedIterator(changeFeedRequestOptions); } public override FeedIterator GetChangeFeedIterator( @@ -416,4 +400,4 @@ public override FeedIterator GetItemQueryIterator( return base.GetItemQueryIterator(feedRange, queryDefinition, continuationToken, requestOptions); } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index aa923deecd..0778edd11a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -55,9 +55,7 @@ public abstract Task TryExecuteQueryAsync( QueryRequestOptions requestOptions, CancellationToken cancellationToken = default); - public abstract FeedIterator GetStandByFeedIterator( - string continuationToken = default, - int? maxItemCount = default, + internal abstract FeedIterator GetStandByFeedIterator( ChangeFeedRequestOptions requestOptions = default); public abstract FeedIteratorInternal GetItemQueryStreamIteratorInternal( @@ -97,29 +95,19 @@ public static void ValidatePartitionKey(object partitionKey, RequestOptions requ public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken = default(CancellationToken)); public abstract FeedIterator GetChangeFeedStreamIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - public abstract FeedIterator GetChangeFeedStreamIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null); public abstract FeedIterator GetChangeFeedStreamIterator( PartitionKey partitionKey, ChangeFeedRequestOptions changeFeedRequestOptions = null); - - public abstract FeedIterator GetChangeFeedIterator( - string continuationToken = null, - ChangeFeedRequestOptions changeFeedRequestOptions = null); public abstract FeedIterator GetChangeFeedIterator( - FeedRange feedRange, ChangeFeedRequestOptions changeFeedRequestOptions = null); public abstract FeedIterator GetChangeFeedIterator( PartitionKey partitionKey, ChangeFeedRequestOptions changeFeedRequestOptions = null); - + public abstract Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default(CancellationToken)); @@ -171,4 +159,4 @@ public QueryPlanIsSupportedResult(QueryIterator queryIterator) public QueryIterator QueryIterator { get; } } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs index 0276699929..8a827b4543 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos { using System; - using System.Diagnostics; using System.IO; using System.Net; using System.Threading; @@ -170,6 +169,7 @@ public async Task CreateContainerAsync( return this.ClientContext.ResponseFactory.CreateContainerResponse(this.GetContainer(containerProperties.Id), response); } + public async Task CreateContainerIfNotExistsAsync( CosmosDiagnosticsContext diagnosticsContext, ContainerProperties containerProperties, diff --git a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedRangeIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedRangeIteratorCore.cs index 61a5042495..55d1301282 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedRangeIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedRangeIteratorCore.cs @@ -49,7 +49,7 @@ public static FeedRangeIteratorCore Create( } // Backward compatible with old format - feedRangeInternal = FeedRangeEPK.ForFullRange(); + feedRangeInternal = FeedRangeEPK.FullRange; feedRangeContinuation = new FeedRangeCompositeContinuation( string.Empty, feedRangeInternal, @@ -65,7 +65,7 @@ public static FeedRangeIteratorCore Create( return new FeedRangeIteratorCore(containerCore, feedRangeContinuation, options, resourceType, queryDefinition); } - feedRangeInternal = feedRangeInternal ?? FeedRangeEPK.ForFullRange(); + feedRangeInternal ??= FeedRangeEPK.FullRange; return new FeedRangeIteratorCore(containerCore, feedRangeInternal, options, resourceType, queryDefinition); } @@ -183,9 +183,12 @@ private async Task ReadNextInternalAsync( streamPayload: stream, requestEnricher: request => { - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(request); + FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(request); this.FeedRangeInternal.Accept(feedRangeVisitor); - this.FeedRangeContinuation.Accept(feedRangeVisitor, QueryRequestOptions.FillContinuationToken); + + FeedRangeContinuationRequestMessagePopulatorVisitor feedRangeContinuationVisitor = new FeedRangeContinuationRequestMessagePopulatorVisitor(request, QueryRequestOptions.FillContinuationToken); + this.FeedRangeContinuation.Accept(feedRangeContinuationVisitor); + if (this.querySpec != null) { request.Headers.Add(HttpConstants.HttpHeaders.ContentType, MediaTypes.QueryJson); diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index 38d5f75be0..f6adc3394b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -22,65 +22,18 @@ namespace Microsoft.Azure.Cosmos /// internal sealed class ChangeFeedIteratorCore : FeedIteratorInternal { - internal FeedRangeInternal FeedRangeInternal; - internal FeedRangeContinuation FeedRangeContinuation { get; private set; } - private readonly ChangeFeedRequestOptions changeFeedOptions; - private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; + private readonly CosmosClientContext clientContext; + private readonly ChangeFeedRequestOptions changeFeedOptions; private readonly AsyncLazy> lazyContainerRid; - private bool hasMoreResults = true; - - public static ChangeFeedIteratorCore Create( - ContainerInternal container, - FeedRangeInternal feedRangeInternal, - string continuation, - ChangeFeedRequestOptions changeFeedRequestOptions) - { - if (!string.IsNullOrEmpty(continuation)) - { - if (FeedRangeContinuation.TryParse(continuation, out FeedRangeContinuation feedRangeContinuation)) - { - return new ChangeFeedIteratorCore(container, feedRangeContinuation, changeFeedRequestOptions); - } - else - { - throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, continuation)); - } - } - - feedRangeInternal = feedRangeInternal ?? FeedRangeEPK.ForFullRange(); - return new ChangeFeedIteratorCore(container, feedRangeInternal, changeFeedRequestOptions); - } - - internal ChangeFeedIteratorCore( - ContainerInternal container, - FeedRangeContinuation feedRangeContinuation, - ChangeFeedRequestOptions changeFeedRequestOptions) - : this(container, feedRangeContinuation.FeedRange, changeFeedRequestOptions) - { - this.FeedRangeContinuation = feedRangeContinuation ?? throw new ArgumentNullException(nameof(feedRangeContinuation)); - } + private bool hasMoreResults; - private ChangeFeedIteratorCore( - ContainerInternal container, - FeedRangeInternal feedRangeInternal, - ChangeFeedRequestOptions changeFeedRequestOptions) - : this(container, changeFeedRequestOptions) - { - this.FeedRangeInternal = feedRangeInternal ?? throw new ArgumentNullException(nameof(feedRangeInternal)); - } + private FeedRangeContinuation FeedRangeContinuation; - private ChangeFeedIteratorCore( + public ChangeFeedIteratorCore( ContainerInternal container, ChangeFeedRequestOptions changeFeedRequestOptions) { - if (changeFeedRequestOptions != null - && changeFeedRequestOptions.MaxItemCount.HasValue - && changeFeedRequestOptions.MaxItemCount.Value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(changeFeedRequestOptions.MaxItemCount)); - } - this.container = container ?? throw new ArgumentNullException(nameof(container)); this.clientContext = container.ClientContext; this.changeFeedOptions = changeFeedRequestOptions ?? new ChangeFeedRequestOptions(); @@ -88,6 +41,27 @@ private ChangeFeedIteratorCore( { return this.TryInitializeContainerRIdAsync(innerCancellationToken); }); + this.hasMoreResults = true; + + if (changeFeedRequestOptions?.From is ChangeFeedRequestOptions.StartFromContinuation startFromContinuation) + { + if (!FeedRangeContinuation.TryParse(startFromContinuation.Continuation, out FeedRangeContinuation feedRangeContinuation)) + { + throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, startFromContinuation.Continuation)); + } + + this.FeedRangeContinuation = feedRangeContinuation; + this.changeFeedOptions.FeedRange = feedRangeContinuation.GetFeedRange(); + string continuationToken = feedRangeContinuation.GetContinuation(); + if (continuationToken != null) + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuationToken); + } + else + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); + } + } } public override bool HasMoreResults => this.hasMoreResults; @@ -97,12 +71,12 @@ private ChangeFeedIteratorCore( /// /// (Optional) representing request cancellation. /// A query response from cosmos service - public override async Task ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken)) + public override async Task ReadNextAsync(CancellationToken cancellationToken = default) { CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(this.changeFeedOptions); using (diagnostics.GetOverallScope()) { - diagnostics.AddDiagnosticsInternal(new FeedRangeStatistics(this.FeedRangeInternal)); + diagnostics.AddDiagnosticsInternal(new FeedRangeStatistics(this.changeFeedOptions.FeedRange)); if (!this.lazyContainerRid.ValueInitialized) { using (diagnostics.CreateScope("InitializeContainerResourceId")) @@ -123,21 +97,27 @@ private ChangeFeedIteratorCore( } } - using (diagnostics.CreateScope("InitializeContinuation")) + if (this.FeedRangeContinuation == null) { - if (this.FeedRangeContinuation != null) + using (diagnostics.CreateScope("InitializeContinuation")) { - TryCatch validateContainer = this.FeedRangeContinuation.ValidateContainer(this.lazyContainerRid.Result.Result); - if (!validateContainer.Succeeded) - { - return CosmosExceptionFactory.CreateBadRequestException( - message: validateContainer.Exception.InnerException.Message, - innerException: validateContainer.Exception.InnerException, - diagnosticsContext: diagnostics).ToCosmosResponseMessage(new RequestMessage(method: null, requestUriString: null, diagnosticsContext: diagnostics)); - } + await this.InitializeFeedContinuationAsync(cancellationToken); } + } - await this.InitializeFeedContinuationAsync(cancellationToken); + TryCatch validateContainer = this.FeedRangeContinuation.ValidateContainer(this.lazyContainerRid.Result.Result); + if (!validateContainer.Succeeded) + { + return CosmosExceptionFactory + .CreateBadRequestException( + message: validateContainer.Exception.InnerException.Message, + innerException: validateContainer.Exception.InnerException, + diagnosticsContext: diagnostics) + .ToCosmosResponseMessage( + new RequestMessage( + method: null, + requestUriString: null, + diagnosticsContext: diagnostics)); } } @@ -145,10 +125,7 @@ private ChangeFeedIteratorCore( } } - public override CosmosElement GetCosmosElementContinuationToken() - { - return CosmosElement.Parse(this.FeedRangeContinuation.ToString()); - } + public override CosmosElement GetCosmosElementContinuationToken() => CosmosElement.Parse(this.FeedRangeContinuation.ToString()); private async Task ReadNextInternalAsync( CosmosDiagnosticsContext diagnosticsScope, @@ -156,20 +133,28 @@ private async Task ReadNextInternalAsync( { cancellationToken.ThrowIfCancellationRequested(); + string continuation = this.FeedRangeContinuation.GetContinuation(); + if (continuation != null) + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(this.FeedRangeContinuation.GetContinuation()); + } + + if ((this.changeFeedOptions.FeedRange == null) || this.changeFeedOptions.FeedRange is FeedRangeEPK) + { + // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId + // So if the range the user supplied is a logical pk value, then we don't want to overwrite it. + this.changeFeedOptions.FeedRange = this.FeedRangeContinuation.GetFeedRange(); + } + ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( resourceUri: this.container.LinkUri, resourceType: ResourceType.Document, operationType: OperationType.ReadFeed, requestOptions: this.changeFeedOptions, cosmosContainerCore: this.container, - requestEnricher: request => - { - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(request); - this.FeedRangeInternal.Accept(feedRangeVisitor); - this.FeedRangeContinuation.Accept(feedRangeVisitor, ChangeFeedRequestOptions.FillContinuationToken); - }, - partitionKey: null, - streamPayload: null, + requestEnricher: default, + partitionKey: default, + streamPayload: default, diagnosticsContext: diagnosticsScope, cancellationToken: cancellationToken); @@ -179,11 +164,11 @@ private async Task ReadNextInternalAsync( } if (responseMessage.IsSuccessStatusCode - || responseMessage.StatusCode == HttpStatusCode.NotModified) + || (responseMessage.StatusCode == HttpStatusCode.NotModified)) { // Change Feed read uses Etag for continuation - this.FeedRangeContinuation.ReplaceContinuation(responseMessage.Headers.ETag); this.hasMoreResults = responseMessage.IsSuccessStatusCode; + this.FeedRangeContinuation.ReplaceContinuation(responseMessage.Headers.ETag); return FeedRangeResponse.CreateSuccess( responseMessage, this.FeedRangeContinuation); @@ -229,45 +214,19 @@ private async Task> TryInitializeContainerRIdAsync(Cancellation private async Task InitializeFeedContinuationAsync(CancellationToken cancellationToken) { + // Initializing FeedRangeContinuation (double init pattern, since async needs to be deffered until the first read). if (this.FeedRangeContinuation == null) { - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); - List> ranges; - if (this.FeedRangeInternal is FeedRangePartitionKey) - { - PartitionKeyDefinition partitionKeyDefinition = await this.container.GetPartitionKeyDefinitionAsync(cancellationToken); - ranges = await this.FeedRangeInternal.GetEffectiveRangesAsync(partitionKeyRangeCache, this.lazyContainerRid.Result.Result, partitionKeyDefinition); - } - else - { - IReadOnlyList pkRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: this.lazyContainerRid.Result.Result, - range: (this.FeedRangeInternal as FeedRangeEPK).Range, - forceRefresh: false); - ranges = pkRanges.Select(pkRange => pkRange.ToRange()).ToList(); - } + FeedRangePartitionKeyRangeExtractor feedRangePartitionKeyRangeExtractor = new FeedRangePartitionKeyRangeExtractor(this.container); - this.FeedRangeContinuation = new FeedRangeCompositeContinuation( - containerRid: this.lazyContainerRid.Result.Result, - feedRange: this.FeedRangeInternal, - ranges: ranges); - } - else if (this.FeedRangeInternal is FeedRangePartitionKeyRange) - { - // Migration from PKRangeId scenario - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); - List> effectiveRanges = await this.FeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: partitionKeyRangeCache, - containerRid: this.lazyContainerRid.Result.Result, - partitionKeyDefinition: null); + IReadOnlyList> ranges = await ((FeedRangeInternal)this.changeFeedOptions.FeedRange).AcceptAsync( + feedRangePartitionKeyRangeExtractor, + cancellationToken); - // Override the original PKRangeId based FeedRange - this.FeedRangeInternal = new FeedRangeEPK(effectiveRanges[0]); this.FeedRangeContinuation = new FeedRangeCompositeContinuation( containerRid: this.lazyContainerRid.Result.Result, - feedRange: this.FeedRangeInternal, - ranges: effectiveRanges, - continuation: this.FeedRangeContinuation.GetContinuation()); + feedRange: (FeedRangeInternal)this.changeFeedOptions.FeedRange, + ranges: ranges); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 220044fe3d..8edcf583e4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Json; /// /// Cosmos Change Feed Iterator for a particular Partition Key Range @@ -19,93 +18,53 @@ internal sealed class ChangeFeedPartitionKeyResultSetIteratorCore : FeedIterator private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; private readonly ChangeFeedRequestOptions changeFeedOptions; - private string continuationToken; - private string partitionKeyRangeId; private bool hasMoreResultsInternal; + private string continuationToken; - internal ChangeFeedPartitionKeyResultSetIteratorCore( + public ChangeFeedPartitionKeyResultSetIteratorCore( CosmosClientContext clientContext, ContainerInternal container, - string partitionKeyRangeId, - string continuationToken, - int? maxItemCount, ChangeFeedRequestOptions options) { - if (container == null) - { - throw new ArgumentNullException(nameof(container)); - } - - if (partitionKeyRangeId == null) - { - throw new ArgumentNullException(nameof(partitionKeyRangeId)); - } - - this.clientContext = clientContext; - this.container = container; + this.clientContext = clientContext ?? throw new ArgumentNullException(nameof(clientContext)); + this.container = container ?? throw new ArgumentNullException(nameof(container)); this.changeFeedOptions = options; - this.MaxItemCount = maxItemCount; - this.continuationToken = continuationToken; - this.partitionKeyRangeId = partitionKeyRangeId; } - /// - /// Gets or sets the maximum number of items to be returned in the enumeration operation in the Azure Cosmos DB service. - /// - public int? MaxItemCount { get; set; } - public override bool HasMoreResults => this.hasMoreResultsInternal; - public override CosmosElement GetCosmosElementContinuationToken() - { - throw new NotImplementedException(); - } + public override CosmosElement GetCosmosElementContinuationToken() => throw new NotImplementedException(); /// /// Get the next set of results from the cosmos service /// /// (Optional) representing request cancellation. /// A change feed response from cosmos service - public override Task ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken)) + public override async Task ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); + // Change Feed read uses Etag for continuation + if (this.continuationToken != null) + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(this.continuationToken); + } - return this.NextResultSetDelegateAsync(this.continuationToken, this.partitionKeyRangeId, this.MaxItemCount, this.changeFeedOptions, cancellationToken) - .ContinueWith(task => - { - ResponseMessage response = task.Result; - // Change Feed uses ETAG - this.continuationToken = response.Headers.ETag; - this.hasMoreResultsInternal = response.StatusCode != HttpStatusCode.NotModified; - response.Headers.ContinuationToken = this.continuationToken; - return response; - }, cancellationToken); - } + ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( + cosmosContainerCore: this.container, + resourceUri: this.container.LinkUri, + resourceType: Documents.ResourceType.Document, + operationType: Documents.OperationType.ReadFeed, + requestOptions: this.changeFeedOptions, + requestEnricher: default, + partitionKey: default, + streamPayload: default, + diagnosticsContext: default, + cancellationToken: cancellationToken); - private Task NextResultSetDelegateAsync( - string continuationToken, - string partitionKeyRangeId, - int? maxItemCount, - ChangeFeedRequestOptions options, - CancellationToken cancellationToken) - { - return this.clientContext.ProcessResourceOperationStreamAsync( - cosmosContainerCore: this.container, - resourceUri: this.container.LinkUri, - resourceType: Documents.ResourceType.Document, - operationType: Documents.OperationType.ReadFeed, - requestOptions: options, - requestEnricher: request => - { - ChangeFeedRequestOptions.FillContinuationToken(request, continuationToken); - ChangeFeedRequestOptions.FillMaxItemCount(request, maxItemCount); - ChangeFeedRequestOptions.FillPartitionKeyRangeId(request, partitionKeyRangeId); - }, - partitionKey: null, - streamPayload: null, - diagnosticsContext: null, - cancellationToken: cancellationToken); + this.continuationToken = responseMessage.Headers.ETag; + this.hasMoreResultsInternal = responseMessage.IsSuccessStatusCode; + responseMessage.Headers.ContinuationToken = this.continuationToken; + return responseMessage; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs index 840dcb544b..278c63bfeb 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Documents.Routing; /// /// Cosmos Stand-By Feed iterator implementing Composite Continuation Token @@ -28,14 +29,10 @@ internal class StandByFeedIteratorCore : FeedIteratorInternal private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; private string containerRid; - private string continuationToken; - private int? maxItemCount; internal StandByFeedIteratorCore( CosmosClientContext clientContext, - ContainerInternal container, - string continuationToken, - int? maxItemCount, + ContainerCore container, ChangeFeedRequestOptions options) { if (container == null) throw new ArgumentNullException(nameof(container)); @@ -43,8 +40,6 @@ internal StandByFeedIteratorCore( this.clientContext = clientContext; this.container = container; this.changeFeedOptions = options; - this.maxItemCount = maxItemCount; - this.continuationToken = continuationToken; } /// @@ -91,7 +86,7 @@ internal StandByFeedIteratorCore( return response; } - internal async Task> ReadNextInternalAsync(CancellationToken cancellationToken) + internal async Task<(string, ResponseMessage)> ReadNextInternalAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -99,13 +94,41 @@ internal async Task> ReadNextInternalAsync(Cancel { PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); this.containerRid = await this.container.GetRIDAsync(cancellationToken); - this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(this.containerRid, this.continuationToken, pkRangeCache.TryGetOverlappingRangesAsync); + + if (this.changeFeedOptions?.From is ChangeFeedRequestOptions.StartFromContinuation startFromContinuation) + { + this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync( + this.containerRid, + startFromContinuation.Continuation, + pkRangeCache.TryGetOverlappingRangesAsync); + (CompositeContinuationToken token, string id) = await this.compositeContinuationToken.GetCurrentTokenAsync(); + + if (token.Token != null) + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token.Token); + } + else + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); + } + } + else + { + this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync( + this.containerRid, + initialStandByFeedContinuationToken: null, + pkRangeCache.TryGetOverlappingRangesAsync); + } } (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync(); - string partitionKeyRangeId = rangeId; - this.continuationToken = currentRangeToken.Token; - ResponseMessage response = await this.NextResultSetDelegateAsync(this.continuationToken, partitionKeyRangeId, this.maxItemCount, this.changeFeedOptions, cancellationToken); + if (currentRangeToken.Token != null) + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(currentRangeToken.Token); + } + + this.changeFeedOptions.FeedRange = new FeedRangePartitionKeyRange(rangeId); + ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken); if (await this.ShouldRetryFailureAsync(response, cancellationToken)) { return await this.ReadNextInternalAsync(cancellationToken); @@ -118,7 +141,7 @@ internal async Task> ReadNextInternalAsync(Cancel currentRangeToken.Token = response.Headers.ETag; } - return new Tuple(partitionKeyRangeId, response); + return (rangeId, response); } /// @@ -146,9 +169,6 @@ internal async Task ShouldRetryFailureAsync( } internal virtual Task NextResultSetDelegateAsync( - string continuationToken, - string partitionKeyRangeId, - int? maxItemCount, ChangeFeedRequestOptions options, CancellationToken cancellationToken) { @@ -159,16 +179,11 @@ internal virtual Task NextResultSetDelegateAsync( operationType: Documents.OperationType.ReadFeed, requestOptions: options, containerInternal: this.container, - requestEnricher: request => - { - ChangeFeedRequestOptions.FillContinuationToken(request, continuationToken); - ChangeFeedRequestOptions.FillMaxItemCount(request, maxItemCount); - ChangeFeedRequestOptions.FillPartitionKeyRangeId(request, partitionKeyRangeId); - }, + requestEnricher: default, responseCreator: response => response, - partitionKey: null, - streamPayload: null, - diagnosticsContext: null, + partitionKey: default, + streamPayload: default, + diagnosticsContext: default, cancellationToken: cancellationToken); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/EstimatorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/EstimatorTests.cs index 43735335d6..9d6159cddd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/EstimatorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/EstimatorTests.cs @@ -82,23 +82,31 @@ public async Task WhenLeasesHaveContinuationTokenNullReturn0() } /// - /// This test checks that when the ContinuationToken is null, we send the StartFromBeginning flag, but since there is no documents, it returns 0 + /// This test checks that when the ContinuationToken is null, + /// we send the StartFromBeginning flag, + /// but since there is no documents, + /// it returns 0 /// /// [TestMethod] public async Task CountPendingDocuments() { ChangeFeedProcessor processor = this.Container - .GetChangeFeedProcessorBuilder("test", (IReadOnlyCollection docs, CancellationToken token) => - { - return Task.CompletedTask; - }) + .GetChangeFeedProcessorBuilder( + processorName: "test", + onChangesDelegate: (IReadOnlyCollection docs, CancellationToken token) => + { + return Task.CompletedTask; + }) .WithInstanceName("random") - .WithLeaseContainer(this.LeaseContainer).Build(); + .WithLeaseContainer(this.LeaseContainer) + .Build(); await processor.StartAsync(); + // Letting processor initialize await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + // Inserting documents foreach (int id in Enumerable.Range(0, 10)) { @@ -111,14 +119,16 @@ public async Task CountPendingDocuments() long? receivedEstimation = null; ChangeFeedProcessor estimator = this.Container - .GetChangeFeedEstimatorBuilder("test", (long estimation, CancellationToken token) => - { - receivedEstimation = estimation; - return Task.CompletedTask; - }, TimeSpan.FromSeconds(1)) - .WithLeaseContainer(this.LeaseContainer).Build(); + .GetChangeFeedEstimatorBuilder( + processorName: "test", + estimationDelegate: (long estimation, CancellationToken token) => + { + receivedEstimation = estimation; + return Task.CompletedTask; + }, TimeSpan.FromSeconds(1)) + .WithLeaseContainer(this.LeaseContainer) + .Build(); - // Inserting more documents foreach (int id in Enumerable.Range(11, 10)) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs index 92c05818c3..56dbc6c5b4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs @@ -130,11 +130,14 @@ public async Task WritesTriggerDelegate_WithInMemoryContainer() return Task.CompletedTask; }) .WithInstanceName("random") - .WithInMemoryLeaseContainer().Build(); + .WithInMemoryLeaseContainer() + .Build(); await processor.StartAsync(); + // Letting processor initialize await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + // Inserting documents foreach (int id in expectedIds) { @@ -144,6 +147,7 @@ public async Task WritesTriggerDelegate_WithInMemoryContainer() // Waiting on all notifications to finish await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedCleanupTime); await processor.StopAsync(); + // Verify that we maintain order CollectionAssert.AreEqual(expectedIds.ToList(), receivedIds); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index 60ee069344..4430700462 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -154,8 +154,13 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() foreach (FeedRange feedRange in feedRanges) { IEnumerable pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime(), MaxItemCount = 1 }; - ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(feedRange: feedRange, changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + FeedRange = feedRange, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + MaxItemCount = 1 + }; + ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) { @@ -163,18 +168,38 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() count += response.Count; } + FeedRangeEPK feedRangeEpk = feedRange as FeedRangeEPK; + // Construct the continuation's range, using PKRangeId + ETag - List ct = new List() { new { min = string.Empty, max = string.Empty, token = firstResponse.Headers.ETag } }; + List ct = new List() + { + new + { + min = feedRangeEpk.Range.Min, + max = feedRangeEpk.Range.Max, + token = firstResponse.Headers.ETag + } + }; + // Extract Etag and manually construct the continuation - dynamic oldContinuation = new { V = 0, PKRangeId = pkRangeIds.First(), Continuation = ct }; + dynamic oldContinuation = new + { + V = 0, + PKRangeId = pkRangeIds.First(), + Continuation = ct + }; continuations.Add(JsonConvert.SerializeObject(oldContinuation)); } // Now start the new iterators with the constructed continuations from migration foreach (string continuation in continuations) { - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { MaxItemCount = 100 }; - ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(continuationToken: continuation, changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + MaxItemCount = 100 + }; + ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs index 2bb67afc78..dfd3dd9c79 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -426,7 +426,7 @@ public async Task ChangeFeedDiagnostics(bool disableDiagnostics) await Task.WhenAll(createItemsTasks); ChangeFeedRequestOptions requestOptions = disableDiagnostics ? ChangeFeedRequestOptionDisableDiagnostic : null; - FeedIterator changeFeedIterator = ((ContainerInternal)(container as ContainerInlineCore)).GetChangeFeedStreamIterator(continuationToken: null, changeFeedRequestOptions: requestOptions); + FeedIterator changeFeedIterator = ((ContainerCore)(container as ContainerInlineCore)).GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions); while (changeFeedIterator.HasMoreResults) { using (ResponseMessage response = await changeFeedIterator.ReadNextAsync()) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs index 48718d65bd..9d2d637788 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query; - using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -65,8 +64,12 @@ public async Task StandByFeedIterator() int pkRangesCount = (await this.Container.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.Container.LinkUri)).Count; await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); - ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator(requestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }); + ContainerCore itemsCore = (ContainerCore)this.Container; + FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); while (feedIterator.HasMoreResults) { @@ -94,7 +97,11 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) // Insert another batch of 25 and use the last continuation token from the first cycle await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); FeedIterator setIteratorNew = - itemsCore.GetStandByFeedIterator(lastcontinuation); + itemsCore.GetStandByFeedIterator( + new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(lastcontinuation), + }); while (setIteratorNew.HasMoreResults) { @@ -144,7 +151,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) } else { - if(!createdDocuments) + if (!createdDocuments) { await this.CreateRandomItems(this.Container, expectedDocuments, randomPartitionKey: true); createdDocuments = true; @@ -182,10 +189,12 @@ public async Task StandByFeedIterator_WithInexistentRange() ContainerInternal itemsCore = this.Container; FeedIterator setIteratorNew = - itemsCore.GetStandByFeedIterator(corruptedTokenSerialized); - - ResponseMessage responseMessage = - await setIteratorNew.ReadNextAsync(this.cancellationToken); + itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(corruptedTokenSerialized), + }); + _ = await setIteratorNew.ReadNextAsync(this.cancellationToken); Assert.Fail("Should have thrown."); } @@ -197,25 +206,27 @@ public async Task StandByFeedIterator_WithInexistentRange() public async Task StandByFeedIterator_WithMaxItemCount() { await this.CreateRandomItems(this.Container, 2, randomPartitionKey: true); - ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator(maxItemCount: 1, requestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }); + ContainerCore itemsCore = (ContainerCore)this.Container; + FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + MaxItemCount = 1, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); while (feedIterator.HasMoreResults) { using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { - if (responseMessage.IsSuccessStatusCode) + Assert.IsTrue(responseMessage.IsSuccessStatusCode, responseMessage.ErrorMessage); + Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; + if (response.Count > 0) { - Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; - if (response.Count > 0) - { - Assert.AreEqual(1, response.Count); - return; - } + Assert.AreEqual(1, response.Count); + return; } } - } Assert.Fail("Found no batch with size 1"); @@ -237,9 +248,13 @@ public async Task StandByFeedIterator_NoFetchNext() int count = 0; while (true) { - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator(continuationToken, requestOptions: requestOptions); + FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + requestOptions: requestOptions); using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { @@ -280,12 +295,15 @@ public async Task StandByFeedIterator_BreathFirst() int expected = 500; List previousToken = null; await this.CreateRandomItems(this.LargerContainer, expected, randomPartitionKey: true); - ContainerInternal itemsCore = this.LargerContainer; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator(maxItemCount: 1, requestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }); - while (true) + ContainerCore itemsCore = (ContainerCore)this.LargerContainer; + FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning() + }); + while (feedIterator.HasMoreResults) { - using (ResponseMessage responseMessage = - await feedIterator.ReadNextAsync(this.cancellationToken)) + using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { string continuationToken = responseMessage.Headers.ContinuationToken; List deserializedToken = JsonConvert.DeserializeObject>(responseMessage.Headers.ContinuationToken); @@ -308,7 +326,13 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) [TestMethod] public async Task StandByFeedIterator_VerifyRefreshIsCalledOnSplit() { - CosmosChangeFeedResultSetIteratorCoreMock iterator = new CosmosChangeFeedResultSetIteratorCoreMock(this.Container, "", 100, new ChangeFeedRequestOptions()); + CosmosChangeFeedResultSetIteratorCoreMock iterator = new CosmosChangeFeedResultSetIteratorCoreMock( + (ContainerCore)this.Container, + new ChangeFeedRequestOptions() + { + MaxItemCount = 100, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); using (ResponseMessage responseMessage = await iterator.ReadNextAsync(this.cancellationToken)) { @@ -341,7 +365,11 @@ public async Task GetChangeFeedTokensAsync_AllowsParallelProcessing() { int count = 0; FeedIterator iteratorForToken = - itemsCore.GetStandByFeedIterator(continuationToken: token, requestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }); + itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token), + }); while (true) { using (ResponseMessage responseMessage = @@ -364,7 +392,7 @@ await iteratorForToken.ReadNextAsync(this.cancellationToken)) await Task.WhenAll(tasks); int documentsRead = 0; - foreach(Task task in tasks) + foreach (Task task in tasks) { documentsRead += task.Result; } @@ -372,6 +400,42 @@ await iteratorForToken.ReadNextAsync(this.cancellationToken)) Assert.AreEqual(totalDocuments, documentsRead); } + [TestMethod] + public async Task GetChangeFeedTokensAsync_DrainFromJustOnePartition() + { + int pkRangesCount = (await this.LargerContainer.ClientContext.DocumentClient.ReadPartitionKeyRangeFeedAsync(this.LargerContainer.LinkUri)).Count; + ContainerInternal itemsCore = this.LargerContainer; + IEnumerable tokens = await itemsCore.GetChangeFeedTokensAsync(); + Assert.IsTrue(pkRangesCount > 1, "Should have created a multi partition container."); + Assert.AreEqual(pkRangesCount, tokens.Count()); + int totalDocuments = 200; + await this.CreateRandomItems(this.LargerContainer, totalDocuments, randomPartitionKey: true); + string token = tokens.First(); + + int count = 0; + FeedIterator iteratorForToken = + itemsCore.GetStandByFeedIterator( + requestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token), + }); + while (iteratorForToken.HasMoreResults) + { + using (ResponseMessage responseMessage = await iteratorForToken.ReadNextAsync(this.cancellationToken)) + { + if (!responseMessage.IsSuccessStatusCode) + { + break; + } + + Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; + count += response.Count; + } + } + + Assert.IsTrue(count > 0 && count < totalDocuments); + } + private async Task> CreateRandomItems(ContainerInternal container, int pkCount, int perPKItemCount = 1, bool randomPartitionKey = true) { Assert.IsFalse(!randomPartitionKey && perPKItemCount > 1); @@ -421,14 +485,10 @@ private class CosmosChangeFeedResultSetIteratorCoreMock : StandByFeedIteratorCor public bool HasCalledForceRefresh = false; internal CosmosChangeFeedResultSetIteratorCoreMock( - ContainerInternal container, - string continuationToken, - int? maxItemCount, + ContainerCore container, ChangeFeedRequestOptions options) : base( clientContext: container.ClientContext, container: container, - continuationToken: continuationToken, - maxItemCount: maxItemCount, options: options) { List compositeContinuationTokens = new List() @@ -459,9 +519,6 @@ internal CosmosChangeFeedResultSetIteratorCoreMock( } internal override Task NextResultSetDelegateAsync( - string continuationToken, - string partitionKeyRangeId, - int? maxItemCount, ChangeFeedRequestOptions options, CancellationToken cancellationToken) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosThroughputTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosThroughputTests.cs index ce6fa8e24f..72fe40d6e0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosThroughputTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosThroughputTests.cs @@ -127,7 +127,7 @@ public async Task DatabaseAutoscaleIfExistsTest() throughputResponse = await containerCore.ReadThroughputIfExistsAsync( requestOptions: null, - default(CancellationToken)); + default); Assert.IsNotNull(throughputResponse); Assert.AreEqual(HttpStatusCode.NotFound, throughputResponse.StatusCode); Assert.IsNull(throughputResponse.Resource); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index fa0916f42e..3ea3f8ace6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -7,17 +7,13 @@ namespace Microsoft.Azure.Cosmos.EmulatorTests.FeedRanges using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Globalization; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Query; - using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Newtonsoft.Json; [SDK.EmulatorTests.TestClass] public class ChangeFeedIteratorCoreTests : BaseCosmosClientHelper @@ -65,7 +61,12 @@ public async Task ChangeFeedIteratorCore_ReadAll() await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + changeFeedRequestOptions: + new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -88,7 +89,11 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); - ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator(continuationToken: continuation, changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + }) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -120,7 +125,11 @@ public async Task ChangeFeedIteratorCore_StartTime() await Task.Delay(1000); await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = now }); + FeedIterator feedIterator = itemsCore.GetChangeFeedStreamIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromTime(now), + }); while (feedIterator.HasMoreResults) { using (ResponseMessage responseMessage = @@ -162,7 +171,12 @@ public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll() } ContainerInternal itemsCore = this.Container; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(new PartitionKey(pkToRead), changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + FeedRange = new FeedRangePartitionKey(new PartitionKey(pkToRead)), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -173,7 +187,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) { Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; totalCount += response.Count; - foreach(ToDoActivity toDoActivity in response) + foreach (ToDoActivity toDoActivity in response) { Assert.AreEqual(pkToRead, toDoActivity.status); } @@ -193,7 +207,11 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) await this.Container.CreateItemAsync(this.CreateRandomToDoActivity(pkToRead)); } - ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator(continuationToken: continuation, changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + }) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -240,7 +258,12 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() } ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(new PartitionKey(pkToRead), changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }); + FeedIterator feedIterator = itemsCore.GetChangeFeedIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + FeedRange = new FeedRangePartitionKey(new PartitionKey(pkToRead)), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); string continuation = null; while (feedIterator.HasMoreResults) { @@ -264,7 +287,11 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() await this.Container.CreateItemAsync(this.CreateRandomToDoActivity(pkToRead)); } - FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(continuationToken: continuation, changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }); + FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator( + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + }); while (setIteratorNew.HasMoreResults) { @@ -292,7 +319,10 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }); + FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); string continuation = null; while (feedIterator.HasMoreResults) { @@ -307,7 +337,10 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); - FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(continuationToken: continuation, changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }); + FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + }); while (setIteratorNew.HasMoreResults) { @@ -366,7 +399,11 @@ public async Task ChangeFeedIteratorCore_WithMaxItemCount() { await this.CreateRandomItems(this.Container, 2, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() { MaxItemCount = 1, StartTime = DateTime.MinValue.ToUniversalTime() }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + MaxItemCount = 1, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) { @@ -406,17 +443,37 @@ public async Task ChangeFeedIteratorCore_NoFetchNext() int count = 0; while (true) { - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }; + ChangeFeedRequestOptions requestOptions; + if (continuation == null) + { + requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }; + } + else + { + requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + }; + } - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(continuationToken: continuation, changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; - using (ResponseMessage responseMessage = - await feedIterator.ReadNextAsync(this.cancellationToken)) + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { if (responseMessage.IsSuccessStatusCode) { Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; count += response.Count; } + else + { + if (responseMessage.StatusCode != HttpStatusCode.NotModified) + { + Assert.Fail(responseMessage.ErrorMessage); + } + } continuation = responseMessage.ContinuationToken; } @@ -428,7 +485,9 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) if (iterations++ > pkRangesCount) { - Assert.Fail("Feed does not contain all elements even after looping through PK ranges. Either the continuation is not moving forward or there is some state problem."); + Assert.Fail("" + + "Feed does not contain all elements even after looping through PK ranges. " + + "Either the continuation is not moving forward or there is some state problem."); } } } @@ -444,7 +503,11 @@ public async Task ChangeFeedIteratorCore_BreathFirst() List previousToken = null; await this.CreateRandomItems(this.LargerContainer, expected, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime(), MaxItemCount = 1 }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + MaxItemCount = 1, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }) as ChangeFeedIteratorCore; while (true) { using (ResponseMessage responseMessage = @@ -489,7 +552,11 @@ public async Task GetFeedRangesAsync_AllowsParallelProcessing() { int count = 0; ChangeFeedIteratorCore iteratorForToken = - itemsCore.GetChangeFeedStreamIterator(token, changeFeedRequestOptions: new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue }) as ChangeFeedIteratorCore; + itemsCore.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() + { + FeedRange = token, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }) as ChangeFeedIteratorCore; while (true) { using (ResponseMessage responseMessage = @@ -524,9 +591,17 @@ await iteratorForToken.ReadNextAsync(this.cancellationToken)) public async Task CannotMixTokensFromOtherContainers() { IReadOnlyList tokens = await this.LargerContainer.GetFeedRangesAsync(); - FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator(tokens[0]); + FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator( + new ChangeFeedRequestOptions() + { + FeedRange = tokens[0], + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); ResponseMessage responseMessage = await iterator.ReadNextAsync(); - iterator = this.Container.GetChangeFeedStreamIterator(responseMessage.ContinuationToken); + iterator = this.Container.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(responseMessage.ContinuationToken), + }); responseMessage = await iterator.ReadNextAsync(); Assert.IsNotNull(responseMessage.CosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqAggregateFunctionsBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqAggregateFunctionsBaselineTests.cs index 1aea11d775..b731ec4722 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqAggregateFunctionsBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqAggregateFunctionsBaselineTests.cs @@ -499,7 +499,7 @@ public void TestAggregateAvg() public override LinqAggregateOutput ExecuteTest(LinqAggregateInput input) { LinqAggregateFunctionBaselineTests.lastExecutedScalarQuery = null; - var compiledQuery = input.expression.Compile(); + Func compiledQuery = input.expression.Compile(); string errorMessage = null; string query = string.Empty; @@ -522,7 +522,7 @@ public override LinqAggregateOutput ExecuteTest(LinqAggregateInput input) try { - var dataResult = compiledQuery(false); + object dataResult = compiledQuery(false); Assert.AreEqual(dataResult, queryResult); } catch (ArgumentException) @@ -567,12 +567,12 @@ public LinqAggregateOutput(string sqlQuery, string errorMessage = null) public override void SerializeAsXml(XmlWriter xmlWriter) { - xmlWriter.WriteStartElement(nameof(SqlQuery)); + xmlWriter.WriteStartElement(nameof(this.SqlQuery)); xmlWriter.WriteCData(LinqTestOutput.FormatSql(this.SqlQuery)); xmlWriter.WriteEndElement(); if (this.ErrorMessage != null) { - xmlWriter.WriteStartElement(nameof(ErrorMessage)); + xmlWriter.WriteStartElement(nameof(this.ErrorMessage)); xmlWriter.WriteCData(LinqTestOutput.FormatErrorMessage(this.ErrorMessage)); xmlWriter.WriteEndElement(); } @@ -601,7 +601,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) throw new ArgumentNullException($"{nameof(xmlWriter)} cannot be null."); } - var expressionString = LinqTestInput.FilterInputExpression(expression.Body.ToString()); + string expressionString = LinqTestInput.FilterInputExpression(this.expression.Body.ToString()); xmlWriter.WriteStartElement("Description"); xmlWriter.WriteCData(this.Description); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/DiagnosticValidators.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/DiagnosticValidators.cs index add0d49f4d..c2827862f5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/DiagnosticValidators.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/DiagnosticValidators.cs @@ -15,7 +15,7 @@ internal static class DiagnosticValidator { public static void ValidatePointOperationDiagnostics(CosmosDiagnosticsContext diagnosticsContext) { - JObject jObject = JObject.Parse(diagnosticsContext.ToString()); + _ = JObject.Parse(diagnosticsContext.ToString()); PointDiagnosticValidatorHelper validator = new PointDiagnosticValidatorHelper(); validator.Visit(diagnosticsContext); validator.Validate(); @@ -23,7 +23,7 @@ public static void ValidatePointOperationDiagnostics(CosmosDiagnosticsContext di public static void ValidateChangeFeedOperationDiagnostics(CosmosDiagnosticsContext diagnosticsContext) { - JObject jObject = JObject.Parse(diagnosticsContext.ToString()); + _ = JObject.Parse(diagnosticsContext.ToString()); ChangeFeedDiagnosticValidatorHelper validator = new ChangeFeedDiagnosticValidatorHelper(); validator.Visit(diagnosticsContext); validator.Validate(); @@ -32,7 +32,7 @@ public static void ValidateChangeFeedOperationDiagnostics(CosmosDiagnosticsConte public static void ValidateQueryDiagnostics(CosmosDiagnosticsContext diagnosticsContext, bool isFirstPage) { - JObject jObject = JObject.Parse(diagnosticsContext.ToString()); + _ = JObject.Parse(diagnosticsContext.ToString()); QueryDiagnosticValidatorHelper validator = new QueryDiagnosticValidatorHelper(); validator.Visit(diagnosticsContext); validator.Validate(isFirstPage); @@ -41,7 +41,7 @@ public static void ValidateQueryDiagnostics(CosmosDiagnosticsContext diagnostics public static void ValidateQueryGatewayPlanDiagnostics(CosmosDiagnosticsContext diagnosticsContext, bool isFirstPage) { string diagnostics = diagnosticsContext.ToString(); - JObject jObject = JObject.Parse(diagnostics); + _ = JObject.Parse(diagnostics); QueryGatewayPlanDiagnosticValidatorHelper validator = new QueryGatewayPlanDiagnosticValidatorHelper(); validator.Visit(diagnosticsContext); validator.Validate(isFirstPage); @@ -108,7 +108,9 @@ private static void ValidateRequestHandlerScope(RequestHandlerScope scope, TimeS if (totalElapsedTime.HasValue) { - Assert.IsTrue(scopeTotalElapsedTime <= totalElapsedTime, $"RequestHandlerScope should not have larger time than the entire context. Scope: {totalElapsedTime} Total: {totalElapsedTime.Value}"); + Assert.IsTrue( + scopeTotalElapsedTime <= totalElapsedTime, + $"RequestHandlerScope should not have larger time than the entire context. Scope: {totalElapsedTime} Total: {totalElapsedTime.Value}"); } string info = scope.ToString(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs index 5310c38007..2dfed154a3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs @@ -115,7 +115,7 @@ public async Task ChangeFeedBaselineAsync() .GetChangeFeedStreamIterator( changeFeedRequestOptions: new ChangeFeedRequestOptions() { - StartTime = DateTime.MinValue.ToUniversalTime() + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedProcessorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedProcessorCoreTests.cs index 315644d7ec..f884c95035 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedProcessorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedProcessorCoreTests.cs @@ -24,7 +24,7 @@ public class ChangeFeedProcessorCoreTests [ExpectedException(typeof(ArgumentNullException))] public void ApplyBuildConfiguration_ValidatesNullStore() { - ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out Mock> factory, out Mock> observer); + ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out _, out _); processor.ApplyBuildConfiguration( null, null, @@ -39,7 +39,7 @@ public void ApplyBuildConfiguration_ValidatesNullStore() [ExpectedException(typeof(ArgumentNullException))] public void ApplyBuildConfiguration_ValidatesNullInstance() { - ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out Mock> factory, out Mock> observer); + ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out _, out _); processor.ApplyBuildConfiguration( Mock.Of(), null, @@ -54,7 +54,7 @@ public void ApplyBuildConfiguration_ValidatesNullInstance() [ExpectedException(typeof(ArgumentNullException))] public void ApplyBuildConfiguration_ValidatesNullMonitoredContainer() { - ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out Mock> factory, out Mock> observer); + ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out _, out _); processor.ApplyBuildConfiguration( Mock.Of(), null, @@ -68,7 +68,7 @@ public void ApplyBuildConfiguration_ValidatesNullMonitoredContainer() [TestMethod] public void ApplyBuildConfiguration_ValidCustomStore() { - ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out Mock> factory, out Mock> observer); + ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out _, out _); processor.ApplyBuildConfiguration( Mock.Of(), null, @@ -82,7 +82,7 @@ public void ApplyBuildConfiguration_ValidCustomStore() [TestMethod] public void ApplyBuildConfiguration_ValidContainerStore() { - ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out Mock> factory, out Mock> observer); + ChangeFeedProcessorCore processor = ChangeFeedProcessorCoreTests.CreateProcessor(out _, out _); processor.ApplyBuildConfiguration( null, ChangeFeedProcessorCoreTests.GetMockedContainer("leases"), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 9a0f8c65d4..825f449408 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -64,7 +64,13 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() DatabaseInternal databaseCore = new DatabaseInlineCore(mockContext.Object, "mydb"); StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( - mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), null, 10, new ChangeFeedRequestOptions()); + mockContext.Object, + new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + new ChangeFeedRequestOptions() + { + MaxItemCount = 10, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); Assert.IsTrue(!firstRequest.Headers.ContinuationToken.Contains(secondResponse.Headers.ETag), "Response should not contain the second continuation"); @@ -120,7 +126,13 @@ public async Task ShouldContinueUntilResponseOk() DatabaseInternal databaseCore = new DatabaseInlineCore(mockContext.Object, "mydb"); StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( - mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), null, 10, new ChangeFeedRequestOptions()); + mockContext.Object, + new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + new ChangeFeedRequestOptions() + { + MaxItemCount = 10, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(secondResponse.Headers.ETag), "Response should contain the second continuation"); @@ -179,7 +191,13 @@ public async Task ShouldReturnNotModifiedAfterCyclingOnAllRanges() DatabaseInternal databaseCore = new DatabaseInlineCore(mockContext.Object, "mydb"); StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( - mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), null, 10, new ChangeFeedRequestOptions()); + mockContext.Object, + new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + new ChangeFeedRequestOptions() + { + MaxItemCount = 10, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(secondResponse.Headers.ETag), "Response should contain the second continuation"); @@ -233,7 +251,13 @@ public async Task ShouldReturnNotModifiedOnSingleRange() DatabaseInternal databaseCore = new DatabaseInlineCore(mockContext.Object, "mydb"); StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( - mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), null, 10, new ChangeFeedRequestOptions()); + mockContext.Object, + new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + new ChangeFeedRequestOptions() + { + MaxItemCount = 10, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); Assert.AreEqual(HttpStatusCode.NotModified, firstRequest.StatusCode); @@ -316,4 +340,4 @@ private class MultiRangeMockDocumentClient : MockDocumentClient } } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index db2f241a25..06bd33c8b5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -3,23 +3,38 @@ "ChangeFeedRequestOptions": { "Subclasses": {}, "Members": { - "System.Nullable`1[System.DateTime] get_StartTime()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.ChangeFeedRequestOptions+StartFrom": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.FeedRange FeedRange": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "System.Nullable`1[System.DateTime] get_StartTime()" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()" }, - "System.Nullable`1[System.DateTime] StartTime": { + "StartFrom From": { "Type": "Property", "Attributes": [], "MethodInfo": null }, - "System.Nullable`1[System.Int32] get_MaxItemCount()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "StartFrom get_From()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], + "MethodInfo": "StartFrom get_From()" + }, + "System.Nullable`1[System.Int32] get_MaxItemCount()": { + "Type": "Method", + "Attributes": [], "MethodInfo": "System.Nullable`1[System.Int32] get_MaxItemCount()" }, "System.Nullable`1[System.Int32] MaxItemCount": { @@ -27,24 +42,118 @@ "Attributes": [], "MethodInfo": null }, + "System.String get_IfMatchEtag()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_IfMatchEtag()" + }, + "System.String get_IfNoneMatchEtag()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_IfNoneMatchEtag()" + }, + "System.String IfMatchEtag[System.ObsoleteAttribute(\"IfMatchEtag is inherited from the base class but not used.\")]-[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)1)]": { + "Type": "Property", + "Attributes": [ + "EditorBrowsableAttribute", + "ObsoleteAttribute" + ], + "MethodInfo": null + }, + "System.String IfNoneMatchEtag[System.ObsoleteAttribute(\"IfNoneMatchEtag is inherited from the base class but not used.\")]-[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)1)]": { + "Type": "Property", + "Attributes": [ + "EditorBrowsableAttribute", + "ObsoleteAttribute" + ], + "MethodInfo": null + }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], "MethodInfo": "Void .ctor()" }, - "Void set_MaxItemCount(System.Nullable`1[System.Int32])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" + "MethodInfo": "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)" }, - "Void set_StartTime(System.Nullable`1[System.DateTime])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_From(StartFrom)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_StartTime(System.Nullable`1[System.DateTime])" + "MethodInfo": "Void set_From(StartFrom)" + }, + "Void set_IfMatchEtag(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_IfMatchEtag(System.String)" + }, + "Void set_IfNoneMatchEtag(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_IfNoneMatchEtag(System.String)" + }, + "Void set_MaxItemCount(System.Nullable`1[System.Int32])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" + } + }, + "NestedTypes": { + "StartFrom": { + "Subclasses": {}, + "Members": { + "StartFrom CreateFromBeginning()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromBeginning()" + }, + "StartFrom CreateFromContinuation(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromContinuation(System.String)" + }, + "StartFrom CreateFromNow()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromNow()" + }, + "StartFrom CreateFromTime(System.DateTime)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" + } + }, + "NestedTypes": {} + } + } + }, + "StartFrom": { + "Subclasses": {}, + "Members": { + "StartFrom CreateFromBeginning()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromBeginning()" + }, + "StartFrom CreateFromContinuation(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromContinuation(System.String)" + }, + "StartFrom CreateFromNow()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromNow()" + }, + "StartFrom CreateFromTime(System.DateTime)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" } }, "NestedTypes": {} @@ -52,41 +161,31 @@ "Container": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { + "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, - "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(System.String, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(System.String, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" - }, "Microsoft.Azure.Cosmos.FeedIterator GetItemQueryStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)": { "Type": "Method", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetItemQueryStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)" }, - "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { + "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, - "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](System.String, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](System.String, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" - }, "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetItemQueryIterator[T](Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)": { "Type": "Method", "Attributes": [], @@ -176,23 +275,38 @@ "ChangeFeedRequestOptions": { "Subclasses": {}, "Members": { - "System.Nullable`1[System.DateTime] get_StartTime()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.ChangeFeedRequestOptions+StartFrom": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.FeedRange FeedRange": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "System.Nullable`1[System.DateTime] get_StartTime()" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()" }, - "System.Nullable`1[System.DateTime] StartTime": { + "StartFrom From": { "Type": "Property", "Attributes": [], "MethodInfo": null }, - "System.Nullable`1[System.Int32] get_MaxItemCount()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "StartFrom get_From()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], + "MethodInfo": "StartFrom get_From()" + }, + "System.Nullable`1[System.Int32] get_MaxItemCount()": { + "Type": "Method", + "Attributes": [], "MethodInfo": "System.Nullable`1[System.Int32] get_MaxItemCount()" }, "System.Nullable`1[System.Int32] MaxItemCount": { @@ -200,27 +314,95 @@ "Attributes": [], "MethodInfo": null }, + "System.String get_IfMatchEtag()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_IfMatchEtag()" + }, + "System.String get_IfNoneMatchEtag()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_IfNoneMatchEtag()" + }, + "System.String IfMatchEtag[System.ObsoleteAttribute(\"IfMatchEtag is inherited from the base class but not used.\")]-[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)1)]": { + "Type": "Property", + "Attributes": [ + "EditorBrowsableAttribute", + "ObsoleteAttribute" + ], + "MethodInfo": null + }, + "System.String IfNoneMatchEtag[System.ObsoleteAttribute(\"IfNoneMatchEtag is inherited from the base class but not used.\")]-[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)1)]": { + "Type": "Property", + "Attributes": [ + "EditorBrowsableAttribute", + "ObsoleteAttribute" + ], + "MethodInfo": null + }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], "MethodInfo": "Void .ctor()" }, - "Void set_MaxItemCount(System.Nullable`1[System.Int32])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" + "MethodInfo": "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)" }, - "Void set_StartTime(System.Nullable`1[System.DateTime])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_From(StartFrom)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_StartTime(System.Nullable`1[System.DateTime])" + "MethodInfo": "Void set_From(StartFrom)" + }, + "Void set_IfMatchEtag(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_IfMatchEtag(System.String)" + }, + "Void set_IfNoneMatchEtag(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_IfNoneMatchEtag(System.String)" + }, + "Void set_MaxItemCount(System.Nullable`1[System.Int32])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" } }, - "NestedTypes": {} + "NestedTypes": { + "StartFrom": { + "Subclasses": {}, + "Members": { + "StartFrom CreateFromBeginning()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromBeginning()" + }, + "StartFrom CreateFromContinuation(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromContinuation(System.String)" + }, + "StartFrom CreateFromNow()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromNow()" + }, + "StartFrom CreateFromTime(System.DateTime)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" + } + }, + "NestedTypes": {} + } + } } }, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index 13a5f0da0f..f8eed1deeb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -21,7 +21,7 @@ public class ChangeFeedIteratorCoreTests [ExpectedException(typeof(ArgumentNullException))] public void ChangeFeedIteratorCore_Null_Container() { - ChangeFeedIteratorCore.Create(null, null, null, new ChangeFeedRequestOptions()); + new ChangeFeedIteratorCore(container: null, new ChangeFeedRequestOptions()); } [DataTestMethod] @@ -30,24 +30,21 @@ public void ChangeFeedIteratorCore_Null_Container() [DataRow(0)] public void ChangeFeedIteratorCore_ValidateOptions(int maxItemCount) { - ChangeFeedIteratorCore.Create(Mock.Of(), null, null, new ChangeFeedRequestOptions() { MaxItemCount = maxItemCount }); + new ChangeFeedIteratorCore( + Mock.Of(), + new ChangeFeedRequestOptions() + { + MaxItemCount = maxItemCount + }); } [TestMethod] public void ChangeFeedIteratorCore_HasMoreResultsDefault() { - ChangeFeedIteratorCore changeFeedIteratorCore = ChangeFeedIteratorCore.Create(Mock.Of(), null, null, null); + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(Mock.Of(), null); Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); } - [TestMethod] - public void ChangeFeedIteratorCore_FeedRange() - { - FeedRangeInternal feedToken = Mock.Of(); - ChangeFeedIteratorCore changeFeedIteratorCore = ChangeFeedIteratorCore.Create(Mock.Of(), feedToken, null, null); - Assert.AreEqual(feedToken, changeFeedIteratorCore.FeedRangeInternal); - } - [TestMethod] public async Task ChangeFeedIteratorCore_ReadNextAsync() { @@ -79,10 +76,8 @@ public async Task ChangeFeedIteratorCore_ReadNextAsync() FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); @@ -93,7 +88,7 @@ public async Task ChangeFeedIteratorCore_ReadNextAsync() .Setup(f => f.HandleChangeFeedNotModified(It.IsAny())) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); Mock.Get(feedToken) @@ -135,10 +130,8 @@ public async Task ChangeFeedIteratorCore_OfT_ReadNextAsync() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); @@ -149,7 +142,7 @@ public async Task ChangeFeedIteratorCore_OfT_ReadNextAsync() .Setup(f => f.HandleChangeFeedNotModified(It.IsAny())) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); bool creatorCalled = false; Func> creator = (ResponseMessage r) => @@ -201,10 +194,8 @@ public async Task ChangeFeedIteratorCore_UpdatesContinuation_On304() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); @@ -215,7 +206,7 @@ public async Task ChangeFeedIteratorCore_UpdatesContinuation_On304() .Setup(f => f.HandleChangeFeedNotModified(It.IsAny())) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); Mock.Get(feedToken) @@ -256,10 +247,8 @@ public async Task ChangeFeedIteratorCore_DoesNotUpdateContinuation_OnError() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); @@ -270,7 +259,7 @@ public async Task ChangeFeedIteratorCore_DoesNotUpdateContinuation_OnError() .Setup(f => f.HandleChangeFeedNotModified(It.IsAny())) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); Assert.IsFalse(changeFeedIteratorCore.HasMoreResults); @@ -314,10 +303,8 @@ public async Task ChangeFeedIteratorCore_Retries() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); @@ -332,7 +319,7 @@ public async Task ChangeFeedIteratorCore_Retries() .Returns(Documents.ShouldRetryResult.NoRetry()) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); Mock.Get(feedToken) @@ -382,15 +369,11 @@ public async Task ChangeFeedIteratorCore_HandlesSplitsThroughPipeline() .Returns("/dbs"); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -398,7 +381,7 @@ public async Task ChangeFeedIteratorCore_HandlesSplitsThroughPipeline() .Setup(f => f.HandleChangeFeedNotModified(It.IsAny())) .Returns(Documents.ShouldRetryResult.NoRetry()); - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerCore, feedToken, null); + ChangeFeedIteratorCore changeFeedIteratorCore = CreateWithCustomFeedToken(containerCore, feedToken); ResponseMessage response = await changeFeedIteratorCore.ReadNextAsync(); @@ -450,5 +433,18 @@ private static CosmosClientContext GetMockedClientContext( return clientContext; } + + private static ChangeFeedIteratorCore CreateWithCustomFeedToken(ContainerInternal containerInternal, FeedRangeContinuation feedToken) + { + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerInternal, changeFeedRequestOptions: default); + System.Reflection.FieldInfo prop = changeFeedIteratorCore + .GetType() + .GetField( + "FeedRangeContinuation", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + prop.SetValue(changeFeedIteratorCore, feedToken); + + return changeFeedIteratorCore; + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeContinuationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeContinuationTests.cs index c541d02ffd..e6546fc78e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeContinuationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeContinuationTests.cs @@ -51,41 +51,6 @@ public void FeedRangeCompositeContinuation_TryParse() Assert.IsFalse(FeedRangeContinuation.TryParse("whatever", out _)); } - [TestMethod] - public void FeedRangeCompositeContinuation_RequestVisitor() - { - const string containerRid = "containerRid"; - const string continuation = "continuation"; - Documents.Routing.Range range = new Documents.Routing.Range("A", "B", true, false); - FeedRangeCompositeContinuation token = new FeedRangeCompositeContinuation(containerRid, Mock.Of(), new List>() { range }, continuation); - RequestMessage requestMessage = new RequestMessage(); - requestMessage.OperationType = Documents.OperationType.ReadFeed; - requestMessage.ResourceType = Documents.ResourceType.Document; - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(requestMessage); - token.Accept(feedRangeVisitor, ChangeFeedRequestOptions.FillContinuationToken); - Assert.AreEqual(range.Min, requestMessage.Properties[HandlerConstants.StartEpkString]); - Assert.AreEqual(range.Max, requestMessage.Properties[HandlerConstants.EndEpkString]); - Assert.AreEqual(continuation, requestMessage.Headers.IfNoneMatch); - Assert.IsTrue(requestMessage.IsPartitionKeyRangeHandlerRequired); - } - - [TestMethod] - public void FeedRangeCompositeContinuation_RequestVisitor_IfEPKAlreadyExists() - { - const string containerRid = "containerRid"; - const string continuation = "continuation"; - string epkString = Guid.NewGuid().ToString(); - Documents.Routing.Range range = new Documents.Routing.Range("A", "B", true, false); - FeedRangeCompositeContinuation token = new FeedRangeCompositeContinuation(containerRid, Mock.Of(), new List>() { range }, continuation); - RequestMessage requestMessage = new RequestMessage(); - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(requestMessage); - requestMessage.Properties[HandlerConstants.StartEpkString] = epkString; - requestMessage.Properties[HandlerConstants.EndEpkString] = epkString; - token.Accept(feedRangeVisitor, ChangeFeedRequestOptions.FillContinuationToken); - Assert.AreEqual(epkString, requestMessage.Properties[HandlerConstants.StartEpkString]); - Assert.AreEqual(epkString, requestMessage.Properties[HandlerConstants.EndEpkString]); - } - [TestMethod] public void FeedRangeCompositeContinuation_ShouldRetry() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeTests.cs index 9ae01625b6..1b40d0ea25 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/FeedRangeTests.cs @@ -166,9 +166,11 @@ public void FeedRangeEPK_RequestVisitor() Documents.Routing.Range range = new Documents.Routing.Range("AA", "BB", true, false); FeedRangeEPK feedRange = new FeedRangeEPK(range); RequestMessage requestMessage = new RequestMessage(); - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(requestMessage); + FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); feedRange.Accept(feedRangeVisitor); - Assert.AreEqual(0, requestMessage.Properties.Count); + Assert.AreEqual(2, requestMessage.Properties.Count); + Assert.AreEqual("AA", requestMessage.Properties[HandlerConstants.StartEpkString]); + Assert.AreEqual("BB", requestMessage.Properties[HandlerConstants.EndEpkString]); } [TestMethod] @@ -177,7 +179,7 @@ public void FeedRangePKRangeId_RequestVisitor() Documents.PartitionKeyRange partitionKeyRange = new Documents.PartitionKeyRange() { Id = Guid.NewGuid().ToString(), MinInclusive = "AA", MaxExclusive = "BB" }; FeedRangePartitionKeyRange feedRangePartitionKeyRange = new FeedRangePartitionKeyRange(partitionKeyRange.Id); RequestMessage requestMessage = new RequestMessage(); - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(requestMessage); + FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); feedRangePartitionKeyRange.Accept(feedRangeVisitor); Assert.IsNotNull(requestMessage.PartitionKeyRangeId); Assert.IsFalse(requestMessage.IsPartitionKeyRangeHandlerRequired); @@ -189,7 +191,7 @@ public void FeedRangePK_RequestVisitor() PartitionKey partitionKey = new PartitionKey("test"); FeedRangePartitionKey feedRangePartitionKey = new FeedRangePartitionKey(partitionKey); RequestMessage requestMessage = new RequestMessage(); - FeedRangeVisitor feedRangeVisitor = new FeedRangeVisitor(requestMessage); + FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); feedRangePartitionKey.Accept(feedRangeVisitor); Assert.AreEqual(partitionKey.InternalKey.ToJsonString(), requestMessage.Headers.PartitionKey); Assert.IsFalse(requestMessage.IsPartitionKeyRangeHandlerRequired); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ReadFeedTokenIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ReadFeedTokenIteratorCoreTests.cs index 91782a4500..bb6c937c07 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ReadFeedTokenIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ReadFeedTokenIteratorCoreTests.cs @@ -31,8 +31,8 @@ public void ReadFeedIteratorCore_Create_Default() { FeedRangeIteratorCore feedTokenIterator = FeedRangeIteratorCore.Create(Mock.Of(), null, null, null); FeedRangeEPK defaultRange = feedTokenIterator.FeedRangeInternal as FeedRangeEPK; - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Min, defaultRange.Range.Min); - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Max, defaultRange.Range.Max); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Min, defaultRange.Range.Min); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Max, defaultRange.Range.Max); Assert.IsNull(feedTokenIterator.FeedRangeContinuation); } @@ -53,8 +53,8 @@ public void ReadFeedIteratorCore_Create_WithContinuation() string continuation = Guid.NewGuid().ToString(); FeedRangeIteratorCore feedTokenIterator = FeedRangeIteratorCore.Create(Mock.Of(), null, continuation, null); FeedRangeEPK defaultRange = feedTokenIterator.FeedRangeInternal as FeedRangeEPK; - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Min, defaultRange.Range.Min); - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Max, defaultRange.Range.Max); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Min, defaultRange.Range.Min); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Max, defaultRange.Range.Max); Assert.IsNotNull(feedTokenIterator.FeedRangeContinuation); Assert.AreEqual(continuation, feedTokenIterator.FeedRangeContinuation.GetContinuation()); } @@ -64,12 +64,12 @@ public void ReadFeedIteratorCore_Create_WithFeedContinuation() { string continuation = Guid.NewGuid().ToString(); - FeedRangeEPK feedRangeEPK = FeedRangeEPK.ForFullRange(); + FeedRangeEPK feedRangeEPK = FeedRangeEPK.FullRange; FeedRangeCompositeContinuation feedRangeSimpleContinuation = new FeedRangeCompositeContinuation(Guid.NewGuid().ToString(), feedRangeEPK, new List>() { feedRangeEPK.Range }, continuation); FeedRangeIteratorCore feedTokenIterator = FeedRangeIteratorCore.Create(Mock.Of(), null, feedRangeSimpleContinuation.ToString(), null); FeedRangeEPK defaultRange = feedTokenIterator.FeedRangeInternal as FeedRangeEPK; - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Min, defaultRange.Range.Min); - Assert.AreEqual(FeedRangeEPK.ForFullRange().Range.Max, defaultRange.Range.Max); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Min, defaultRange.Range.Min); + Assert.AreEqual(FeedRangeEPK.FullRange.Range.Max, defaultRange.Range.Max); Assert.IsNotNull(feedTokenIterator.FeedRangeContinuation); Assert.AreEqual(continuation, feedTokenIterator.FeedRangeContinuation.GetContinuation()); } @@ -105,13 +105,11 @@ public async Task ReadFeedIteratorCore_ReadNextAsync() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -163,13 +161,11 @@ public async Task ReadFeedIteratorCore_ReadNextAsync_Conflicts() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -222,13 +218,11 @@ public async Task ReadFeedIteratorCore_ReadNextAsync_Conflicts_Query() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -280,13 +274,11 @@ public async Task ReadFeedIteratorCore_OfT_ReadNextAsync() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -347,13 +339,11 @@ public async Task ReadFeedIteratorCore_UpdatesContinuation_OnOK() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -406,13 +396,11 @@ public async Task ReadFeedIteratorCore_DoesNotUpdateContinuation_OnError() .Returns(cosmosClientContext.Object); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -510,13 +498,11 @@ public async Task ReadFeedIteratorCore_HandlesSplitsThroughPipeline() .Returns("/dbs/db/colls/colls"); FeedRangeInternal range = Mock.Of(); Mock.Get(range) - .Setup(f => f.Accept(It.IsAny())); + .Setup(f => f.Accept(It.IsAny())); FeedRangeContinuation feedToken = Mock.Of(); Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); - Mock.Get(feedToken) - .Setup(f => f.Accept(It.IsAny(), It.IsAny>())); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/FeedOptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/FeedOptionTests.cs index 05769e1fee..54c965cce8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/FeedOptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/FeedOptionTests.cs @@ -47,7 +47,7 @@ public async Task CheckConsistencyLevel() public void TestCopyConstructor() { FeedOptions fo = new FeedOptions(); - FeedOptions f01 = new FeedOptions(fo); + _ = new FeedOptions(fo); } internal class TestQueryExecutionContext : DocumentQueryExecutionContextBase diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index af8009d57e..66564e856c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -224,24 +224,28 @@ public void ConstructorWithRangeGeneratesSingleQueue() public void ChangeFeedRequestOptions_ContinuationIsSet() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions(){ }; - - ChangeFeedRequestOptions.FillContinuationToken(request, "something"); + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation("something"), + }; requestOptions.PopulateRequestOptions(request); - Assert.AreEqual("something", request.Headers.IfNoneMatch); + Assert.AreEqual(expected: "something", actual: request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); } [TestMethod] - public void ChangeFeedRequestOptions_DefaultValues() + public void ChangeFeedRequestOptions_StartFromNow() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { }; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromNow(), + }; requestOptions.PopulateRequestOptions(request); - Assert.AreEqual(ChangeFeedRequestOptions.IfNoneMatchAllHeaderValue, request.Headers.IfNoneMatch); + Assert.AreEqual(expected: "*", request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); } @@ -249,7 +253,10 @@ public void ChangeFeedRequestOptions_DefaultValues() public void ChangeFeedRequestOptions_StartFromBeginning() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { StartTime = DateTime.MinValue.ToUniversalTime() }; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + }; requestOptions.PopulateRequestOptions(request); @@ -258,32 +265,32 @@ public void ChangeFeedRequestOptions_StartFromBeginning() } [TestMethod] - public void ChangeFeedRequestOptions_MaxItemSizeIsSet() + public void ChangeFeedRequestOptions_Default() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { }; + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + }; - ChangeFeedRequestOptions.FillMaxItemCount(request, 10); requestOptions.PopulateRequestOptions(request); - Assert.AreEqual("10", request.Headers[Documents.HttpConstants.HttpHeaders.PageSize]); - Assert.AreEqual(ChangeFeedRequestOptions.IfNoneMatchAllHeaderValue, request.Headers.IfNoneMatch); + Assert.AreEqual(expected: "*", request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); } [TestMethod] - public void ChangeFeedRequestOptions_ContinuationBeatsStartTime() + public void ChangeFeedRequestOptions_MaxItemSizeIsSet() { RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - StartTime = new DateTime(1985, 1, 1) + MaxItemCount = 10, + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }; - - ChangeFeedRequestOptions.FillContinuationToken(request, "something"); requestOptions.PopulateRequestOptions(request); - Assert.AreEqual("something", request.Headers.IfNoneMatch); + Assert.AreEqual(expected: "10", actual: request.Headers[Documents.HttpConstants.HttpHeaders.PageSize]); + Assert.IsNull(request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); } @@ -293,12 +300,13 @@ public void ChangeFeedRequestOptions_AddsStartTime() RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - StartTime = new DateTime(1985, 1, 1, 0, 0,0, DateTimeKind.Utc) + From = ChangeFeedRequestOptions.StartFrom.CreateFromTime(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)), }; - requestOptions.PopulateRequestOptions(request); - Assert.AreEqual("Tue, 01 Jan 1985 00:00:00 GMT", request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); + Assert.AreEqual( + expected: "Tue, 01 Jan 1985 00:00:00 GMT", + actual: request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); Assert.IsNull(request.Headers.IfNoneMatch); } @@ -306,11 +314,13 @@ public void ChangeFeedRequestOptions_AddsStartTime() public void ChangeFeedRequestOptions_AddsPartitionKeyRangeId() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions(); - - ChangeFeedRequestOptions.FillPartitionKeyRangeId(request, "randomPK"); + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + FeedRange = new FeedRangePartitionKeyRange("randomPK") + }; + requestOptions.PopulateRequestOptions(request); - Assert.AreEqual("randomPK", request.PartitionKeyRangeId.PartitionKeyRangeId); + Assert.AreEqual(expected: "randomPK", actual: request.PartitionKeyRangeId.PartitionKeyRangeId); } private static StandByFeedContinuationToken.PartitionKeyRangeCacheDelegate CreateCacheFromRange(IReadOnlyList keyRanges) From 03987506e965ddaf23b06705a08ac8f644a0efb5 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 9 Jul 2020 11:06:07 -0700 Subject: [PATCH 02/19] marking test as ignored as agreed upon --- .../Contracts/ContractTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index 4430700462..0f5c95c7a3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -135,6 +135,7 @@ public async Task ItemStreamContractVerifier() /// [TestMethod] [Timeout(30000)] + [Ignore] public async Task ChangeFeed_FeedRange_FromV2SDK() { ContainerResponse largerContainer = await this.database.CreateContainerAsync( From 3d67d53dade5ffafc4dec660b5df2bfd3b48ae19 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 23 Jul 2020 12:44:33 -0700 Subject: [PATCH 03/19] wired up changes --- .../Utils/ResultSetIteratorUtils.cs | 11 +- .../src/FeedRange/FeedRange.cs | 2 + .../ChangeFeedRequestOptions.cs | 202 ++++++++++++++---- .../src/Resource/Container/ContainerCore.cs | 24 --- .../Resource/Container/ContainerInlineCore.cs | 14 -- .../Resource/Container/ContainerInternal.cs | 9 - .../QueryResponses/ChangeFeedIteratorCore.cs | 42 ++-- .../QueryResponses/StandByFeedIteratorCore.cs | 8 +- .../Contracts/ContractTests.cs | 3 +- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 12 +- .../StandByFeedContinuationTokenTests.cs | 31 ++- 11 files changed, 219 insertions(+), 139 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs index 0d80835da2..66e3cdcce1 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs @@ -16,28 +16,31 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore BuildResultSetIterator DateTime? startTime, bool startFromBeginning) { + FeedRange feedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId); + ChangeFeedRequestOptions.StartFrom startFrom; if (continuationToken != null) { + // For continuation based feed range we need to manufactor a new continuation token that has the partition key range id in it. startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuationToken); + throw new NotImplementedException("Need to implement after I see what the continuation token looks like."); } else if (startTime.HasValue) { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromTime(startTime.Value); + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromTimeWithRange(startTime.Value, feedRange); } else if (startFromBeginning) { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange); } else { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromNow(); + startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromNowWithRange(feedRange); } ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { MaxItemCount = maxItemCount, - FeedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId), From = startFrom, }; diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs index 61f94044ee..2e58afc279 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs @@ -38,5 +38,7 @@ public static FeedRange FromJsonString(string toStringValue) return parsedRange; } + + public static FeedRange CreateFromPartitionKey(PartitionKey partitionKey) => new FeedRangePartitionKey(partitionKey); } } diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index 038e4dec74..44d8f57c73 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -47,12 +47,7 @@ public int? MaxItemCount /// /// Only applies in the case where no FeedToken is provided or the FeedToken was never used in a previous iterator. /// - public StartFrom From { get; set; } = StartFromNow.Singleton; - - /// - /// Gets or set which ranges to execute the ChangeFeed operation on. - /// - public FeedRange FeedRange { get; set; } = FeedRangeEPK.FullRange; + public StartFrom From { get; set; } = new StartFromNow(FeedRangeEPK.FullRange); /// /// Fill the CosmosRequestMessage headers with the set properties @@ -79,12 +74,6 @@ internal override void PopulateRequestOptions(RequestMessage request) this.MaxItemCount.Value.ToString(CultureInfo.InvariantCulture)); } - if (this.FeedRange != null) - { - FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(request); - ((FeedRangeInternal)this.FeedRange).Accept(feedRangeVisitor); - } - request.Headers.Add( HttpConstants.HttpHeaders.A_IM, HttpConstants.A_IMHeaderValues.IncrementalFeed); @@ -121,19 +110,34 @@ public abstract class StartFrom /// /// Initializes an instance of the class. /// - protected StartFrom() + internal StartFrom() { + // Internal so people can't derive from this type. } internal abstract void Accept(StartFromVisitor visitor); + internal abstract TResult Accept(StartFromVisitor visitor); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static StartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEPK.FullRange); + /// /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. /// + /// The range to start from. /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static StartFrom CreateFromNow() + public static StartFrom CreateFromNowWithRange(FeedRange feedRange) { - return StartFromNow.Singleton; + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new StartFromNow(feedRangeInternal); } /// @@ -141,9 +145,22 @@ public static StartFrom CreateFromNow() /// /// The time to start reading from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static StartFrom CreateFromTime(DateTime dateTime) + public static StartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEPK.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time to start reading from. + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static StartFrom CreateFromTimeWithRange(DateTime dateTime, FeedRange feedRange) { - return new StartFromTime(dateTime); + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new StartFromTime(dateTime, feedRangeInternal); } /// @@ -151,18 +168,27 @@ public static StartFrom CreateFromTime(DateTime dateTime) /// /// The continuation to resume from. /// A that tells the ChangeFeed operation to start reading changes from a save point. - public static StartFrom CreateFromContinuation(string continuation) - { - return new StartFromContinuation(continuation); - } + public static StartFrom CreateFromContinuation(string continuation) => new StartFromContinuation(continuation); /// /// Creates a that tells the ChangeFeed operation to start from the beginning of time. /// /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static StartFrom CreateFromBeginning() + public static StartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEPK.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static StartFrom CreateFromBeginningWithRange(FeedRange feedRange) { - return StartFromBeginning.Singleton; + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new StartFromBeginning(feedRangeInternal); } } @@ -172,6 +198,16 @@ internal abstract class StartFromVisitor public abstract void Visit(StartFromTime startFromTime); public abstract void Visit(StartFromContinuation startFromContinuation); public abstract void Visit(StartFromBeginning startFromBeginning); + public abstract void Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange); + } + + internal abstract class StartFromVisitor + { + public abstract TResult Visit(StartFromNow startFromNow); + public abstract TResult Visit(StartFromTime startFromTime); + public abstract TResult Visit(StartFromContinuation startFromContinuation); + public abstract TResult Visit(StartFromBeginning startFromBeginning); + public abstract TResult Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange); } internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor @@ -180,15 +216,22 @@ internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); private readonly RequestMessage requestMessage; + private readonly FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor; public PopulateStartFromRequestOptionVisitor(RequestMessage requestMessage) { this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); + this.feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); } public override void Visit(StartFromNow startFromNow) { this.requestMessage.Headers.IfNoneMatch = PopulateStartFromRequestOptionVisitor.IfNoneMatchAllHeaderValue; + + if (startFromNow.FeedRange != null) + { + startFromNow.FeedRange.Accept(this.feedRangeVisitor); + } } public override void Visit(StartFromTime startFromTime) @@ -204,6 +247,8 @@ public override void Visit(StartFromTime startFromTime) HttpConstants.HttpHeaders.IfModifiedSince, startFromTime.Time.ToString("r", CultureInfo.InvariantCulture)); } + + startFromTime.FeedRange.Accept(this.feedRangeVisitor); } public override void Visit(StartFromContinuation startFromContinuation) @@ -215,28 +260,63 @@ public override void Visit(StartFromContinuation startFromContinuation) public override void Visit(StartFromBeginning startFromBeginning) { // We don't need to set any headers to start from the beginning + + // Except for the feed range. + startFromBeginning.FeedRange.Accept(this.feedRangeVisitor); + } + + public override void Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; + + startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); } } + internal sealed class FeedRangeExtractor : StartFromVisitor + { + public static readonly FeedRangeExtractor Singleton = new FeedRangeExtractor(); + + private FeedRangeExtractor() + { + } + + public override FeedRange Visit(StartFromNow startFromNow) => startFromNow.FeedRange; + + public override FeedRange Visit(StartFromTime startFromTime) => startFromTime.FeedRange; + + public override FeedRange Visit(StartFromContinuation startFromContinuation) + => throw new NotSupportedException($"{nameof(StartFromContinuation)} does not have a feed range."); + + public override FeedRange Visit(StartFromBeginning startFromBeginning) => startFromBeginning.FeedRange; + + public override FeedRange Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange) => startFromContinuationAndFeedRange.FeedRange; + } + /// /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. /// internal sealed class StartFromNow : StartFrom { - public static readonly StartFromNow Singleton = new StartFromNow(); - /// /// Intializes an instance of the class. /// - public StartFromNow() + /// The (optional) feed range to start from. + public StartFromNow(FeedRangeInternal feedRange) : base() { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } - internal override void Accept(StartFromVisitor visitor) - { - visitor.Visit(this); - } + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); } /// @@ -248,7 +328,8 @@ internal sealed class StartFromTime : StartFrom /// Initializes an instance of the class. /// /// The time to start reading from. - public StartFromTime(DateTime time) + /// The (optional) range to start from. + public StartFromTime(DateTime time, FeedRangeInternal feedRange) : base() { if (time.Kind != DateTimeKind.Utc) @@ -257,6 +338,7 @@ public StartFromTime(DateTime time) } this.Time = time; + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } /// @@ -264,15 +346,20 @@ public StartFromTime(DateTime time) /// public DateTime Time { get; } - internal override void Accept(StartFromVisitor visitor) - { - visitor.Visit(this); - } + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); } /// /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. /// + /// This class is used to temporarily store the fully serialized composite continuation token and needs to transformed into a . internal sealed class StartFromContinuation : StartFrom { /// @@ -295,10 +382,9 @@ public StartFromContinuation(string continuation) /// public string Continuation { get; } - internal override void Accept(StartFromVisitor visitor) - { - visitor.Visit(this); - } + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); } /// @@ -306,17 +392,44 @@ internal override void Accept(StartFromVisitor visitor) /// internal sealed class StartFromBeginning : StartFrom { - public static readonly StartFromBeginning Singleton = new StartFromBeginning(); - - public StartFromBeginning() + /// + /// Initializes an instance of the class. + /// + /// The (optional) range to start from. + public StartFromBeginning(FeedRangeInternal feedRange) : base() { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } - internal override void Accept(StartFromVisitor visitor) + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading from an LSN for a particular feed range. + /// + internal sealed class StartFromContinuationAndFeedRange : StartFrom + { + public StartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) { - visitor.Visit(this); + this.Etag = etag ?? throw new ArgumentNullException(nameof(etag)); + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } + + public string Etag { get; } + + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); } internal ChangeFeedRequestOptions Clone() @@ -325,7 +438,6 @@ internal ChangeFeedRequestOptions Clone() { MaxItemCount = this.maxItemCount, From = this.From, - FeedRange = this.FeedRange, }; } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 21459f19af..d47ec52344 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -258,33 +258,9 @@ public override FeedIterator GetChangeFeedStreamIterator( changeFeedRequestOptions: changeFeedRequestOptions); } - public override FeedIterator GetChangeFeedStreamIterator( - PartitionKey partitionKey, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - changeFeedRequestOptions ??= new ChangeFeedRequestOptions(); - changeFeedRequestOptions.FeedRange = new FeedRangePartitionKey(partitionKey); - return new ChangeFeedIteratorCore( - container: this, - changeFeedRequestOptions: changeFeedRequestOptions); - } - - public override FeedIterator GetChangeFeedIterator( - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( - container: this, - changeFeedRequestOptions: changeFeedRequestOptions); - - return new FeedIteratorCore(changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); - } - public override FeedIterator GetChangeFeedIterator( - PartitionKey partitionKey, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - changeFeedRequestOptions ??= new ChangeFeedRequestOptions(); - changeFeedRequestOptions.FeedRange = new FeedRangePartitionKey(partitionKey); ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( container: this, changeFeedRequestOptions: changeFeedRequestOptions); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index e3899c9bc2..fdc3edad50 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -378,26 +378,12 @@ public override FeedIterator GetChangeFeedStreamIterator( return base.GetChangeFeedStreamIterator(changeFeedRequestOptions); } - public override FeedIterator GetChangeFeedStreamIterator( - PartitionKey partitionKey, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return base.GetChangeFeedStreamIterator(partitionKey, changeFeedRequestOptions); - } - public override FeedIterator GetChangeFeedIterator( ChangeFeedRequestOptions changeFeedRequestOptions = null) { return base.GetChangeFeedIterator(changeFeedRequestOptions); } - public override FeedIterator GetChangeFeedIterator( - PartitionKey partitionKey, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return base.GetChangeFeedIterator(partitionKey, changeFeedRequestOptions); - } - public override Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index 5c53798e69..599fb1539c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -7,7 +7,6 @@ namespace Microsoft.Azure.Cosmos using System; using System.Collections.Generic; using System.IO; - using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query; @@ -113,17 +112,9 @@ public abstract Task> PatchItemAsync( public abstract FeedIterator GetChangeFeedStreamIterator( ChangeFeedRequestOptions changeFeedRequestOptions = null); - public abstract FeedIterator GetChangeFeedStreamIterator( - PartitionKey partitionKey, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - public abstract FeedIterator GetChangeFeedIterator( ChangeFeedRequestOptions changeFeedRequestOptions = null); - public abstract FeedIterator GetChangeFeedIterator( - PartitionKey partitionKey, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - public abstract Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default(CancellationToken)); diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index f6adc3394b..6366eddb87 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -51,16 +51,10 @@ public ChangeFeedIteratorCore( } this.FeedRangeContinuation = feedRangeContinuation; - this.changeFeedOptions.FeedRange = feedRangeContinuation.GetFeedRange(); - string continuationToken = feedRangeContinuation.GetContinuation(); - if (continuationToken != null) - { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuationToken); - } - else - { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); - } + FeedRange feedRange = feedRangeContinuation.GetFeedRange(); + string etag = feedRangeContinuation.GetContinuation(); + + this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } } @@ -76,7 +70,9 @@ public override async Task ReadNextAsync(CancellationToken canc CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(this.changeFeedOptions); using (diagnostics.GetOverallScope()) { - diagnostics.AddDiagnosticsInternal(new FeedRangeStatistics(this.changeFeedOptions.FeedRange)); + diagnostics.AddDiagnosticsInternal( + new FeedRangeStatistics( + this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton))); if (!this.lazyContainerRid.ValueInitialized) { using (diagnostics.CreateScope("InitializeContainerResourceId")) @@ -133,17 +129,18 @@ private async Task ReadNextInternalAsync( { cancellationToken.ThrowIfCancellationRequested(); - string continuation = this.FeedRangeContinuation.GetContinuation(); - if (continuation != null) + string etag = this.FeedRangeContinuation.GetContinuation(); + if (etag != null) { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(this.FeedRangeContinuation.GetContinuation()); - } + FeedRange feedRange = this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton); + if ((feedRange == null) || feedRange is FeedRangeEPK) + { + // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId + // So if the range the user supplied is a logical pk value, then we don't want to overwrite it. + feedRange = this.FeedRangeContinuation.GetFeedRange(); + } - if ((this.changeFeedOptions.FeedRange == null) || this.changeFeedOptions.FeedRange is FeedRangeEPK) - { - // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId - // So if the range the user supplied is a logical pk value, then we don't want to overwrite it. - this.changeFeedOptions.FeedRange = this.FeedRangeContinuation.GetFeedRange(); + this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( @@ -219,13 +216,14 @@ private async Task InitializeFeedContinuationAsync(CancellationToken cancellatio { FeedRangePartitionKeyRangeExtractor feedRangePartitionKeyRangeExtractor = new FeedRangePartitionKeyRangeExtractor(this.container); - IReadOnlyList> ranges = await ((FeedRangeInternal)this.changeFeedOptions.FeedRange).AcceptAsync( + FeedRange feedRange = this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton); + IReadOnlyList> ranges = await ((FeedRangeInternal)feedRange).AcceptAsync( feedRangePartitionKeyRangeExtractor, cancellationToken); this.FeedRangeContinuation = new FeedRangeCompositeContinuation( containerRid: this.lazyContainerRid.Result.Result, - feedRange: (FeedRangeInternal)this.changeFeedOptions.FeedRange, + feedRange: (FeedRangeInternal)feedRange, ranges: ranges); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs index 278c63bfeb..e9248056bb 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs @@ -122,12 +122,16 @@ internal StandByFeedIteratorCore( } (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync(); + FeedRange feedRange = new FeedRangePartitionKeyRange(rangeId); if (currentRangeToken.Token != null) { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(currentRangeToken.Token); + this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange); + } + else + { + this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange); } - this.changeFeedOptions.FeedRange = new FeedRangePartitionKeyRange(rangeId); ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken); if (await this.ShouldRetryFailureAsync(response, cancellationToken)) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index 0f5c95c7a3..51b9b0ab03 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -157,8 +157,7 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() IEnumerable pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - FeedRange = feedRange, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange), MaxItemCount = 1 }; ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index 3ea3f8ace6..83a7d4d9c9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -174,8 +174,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll() ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( changeFeedRequestOptions: new ChangeFeedRequestOptions() { - FeedRange = new FeedRangePartitionKey(new PartitionKey(pkToRead)), - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead))), }) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) @@ -261,8 +260,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() FeedIterator feedIterator = itemsCore.GetChangeFeedIterator( changeFeedRequestOptions: new ChangeFeedRequestOptions() { - FeedRange = new FeedRangePartitionKey(new PartitionKey(pkToRead)), - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead))), }); string continuation = null; while (feedIterator.HasMoreResults) @@ -554,8 +552,7 @@ public async Task GetFeedRangesAsync_AllowsParallelProcessing() ChangeFeedIteratorCore iteratorForToken = itemsCore.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() { - FeedRange = token, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(token), }) as ChangeFeedIteratorCore; while (true) { @@ -594,8 +591,7 @@ public async Task CannotMixTokensFromOtherContainers() FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator( new ChangeFeedRequestOptions() { - FeedRange = tokens[0], - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(tokens[0]), }); ResponseMessage responseMessage = await iterator.ReadNextAsync(); iterator = this.Container.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index 66564e856c..3390924e5e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -311,16 +311,29 @@ public void ChangeFeedRequestOptions_AddsStartTime() } [TestMethod] - public void ChangeFeedRequestOptions_AddsPartitionKeyRangeId() + public void ChangeFeedRequestOptions_AddsFeedRange() { - RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + FeedRange feedRange = new FeedRangePartitionKeyRange("randomPK"); + ChangeFeedRequestOptions.StartFrom[] froms = new ChangeFeedRequestOptions.StartFrom[] { - FeedRange = new FeedRangePartitionKeyRange("randomPK") + ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange), + ChangeFeedRequestOptions.StartFrom.CreateFromNowWithRange(feedRange), + ChangeFeedRequestOptions.StartFrom.CreateFromTimeWithRange(DateTime.MinValue.ToUniversalTime(), feedRange) }; - requestOptions.PopulateRequestOptions(request); - Assert.AreEqual(expected: "randomPK", actual: request.PartitionKeyRangeId.PartitionKeyRangeId); + foreach (ChangeFeedRequestOptions.StartFrom from in froms) + { + RequestMessage request = new RequestMessage(); + ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() + { + From = from, + }; + requestOptions.PopulateRequestOptions(request); + + Assert.AreEqual( + expected: "randomPK", + actual: request.PartitionKeyRangeId.PartitionKeyRangeId); + } } private static StandByFeedContinuationToken.PartitionKeyRangeCacheDelegate CreateCacheFromRange(IReadOnlyList keyRanges) @@ -332,15 +345,15 @@ private static StandByFeedContinuationToken.PartitionKeyRangeCacheDelegate Creat return Task.FromResult(keyRanges); } - IReadOnlyList filteredRanges = new List(keyRanges.Where(range=> range.MinInclusive.CompareTo(ranges.Min) >= 0 && range.MaxExclusive.CompareTo(ranges.Max) <= 0)); + IReadOnlyList filteredRanges = new List(keyRanges.Where(range => range.MinInclusive.CompareTo(ranges.Min) >= 0 && range.MaxExclusive.CompareTo(ranges.Max) <= 0)); return Task.FromResult(filteredRanges); }; } private static CompositeContinuationToken BuildTokenForRange( - string min, - string max, + string min, + string max, string token) { return new CompositeContinuationToken() From cb697d9202736e4f4d7d2ce9bdbc3c7a46f40c1b Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 23 Jul 2020 19:25:49 -0700 Subject: [PATCH 04/19] resolved iteration comments --- .../src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs | 5 ++--- .../src/RequestOptions/ChangeFeedRequestOptions.cs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs index 66e3cdcce1..dc8fbbe7f3 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs @@ -16,14 +16,13 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore BuildResultSetIterator DateTime? startTime, bool startFromBeginning) { - FeedRange feedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId); + FeedRangeInternal feedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId); ChangeFeedRequestOptions.StartFrom startFrom; if (continuationToken != null) { // For continuation based feed range we need to manufactor a new continuation token that has the partition key range id in it. - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuationToken); - throw new NotImplementedException("Need to implement after I see what the continuation token looks like."); + startFrom = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(continuationToken, feedRange); } else if (startTime.HasValue) { diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index 44d8f57c73..2d42bed80e 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -110,7 +110,7 @@ public abstract class StartFrom /// /// Initializes an instance of the class. /// - internal StartFrom() + internal protected StartFrom() { // Internal so people can't derive from this type. } From 990e8b01a9123ecf4c564d26c401aa9a7d61d329 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 6 Aug 2020 14:09:36 -0700 Subject: [PATCH 05/19] merged --- .../src/RequestOptions/ChangeFeedRequestOptions.cs | 8 ++++---- .../src/Resource/QueryResponses/ChangeFeedIteratorCore.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index 2d42bed80e..9bc80b9a8d 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -47,7 +47,7 @@ public int? MaxItemCount /// /// Only applies in the case where no FeedToken is provided or the FeedToken was never used in a previous iterator. /// - public StartFrom From { get; set; } = new StartFromNow(FeedRangeEPK.FullRange); + public StartFrom From { get; set; } = new StartFromNow(FeedRangeEpk.FullRange); /// /// Fill the CosmosRequestMessage headers with the set properties @@ -123,7 +123,7 @@ internal protected StartFrom() /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. /// /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static StartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEPK.FullRange); + public static StartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. @@ -145,7 +145,7 @@ public static StartFrom CreateFromNowWithRange(FeedRange feedRange) /// /// The time to start reading from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static StartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEPK.FullRange); + public static StartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. @@ -174,7 +174,7 @@ public static StartFrom CreateFromTimeWithRange(DateTime dateTime, FeedRange fee /// Creates a that tells the ChangeFeed operation to start from the beginning of time. /// /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static StartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEPK.FullRange); + public static StartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start from the beginning of time. diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index 6844b0d5e1..8ecb2dcff8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -130,7 +130,7 @@ private async Task ReadNextInternalAsync( if (etag != null) { FeedRange feedRange = this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton); - if ((feedRange == null) || feedRange is FeedRangeEPK) + if ((feedRange == null) || feedRange is FeedRangeEpk) { // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId // So if the range the user supplied is a logical pk value, then we don't want to overwrite it. From 72ecf9600b252809ee4ff16dc13905fbe80d7ab0 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 6 Aug 2020 16:08:28 -0700 Subject: [PATCH 06/19] made start from a required field --- .../Utils/ResultSetIteratorUtils.cs | 10 +- .../src/ChangeFeedStartFrom.cs | 346 ++++++++++++++++++ .../ChangeFeedRequestOptions.cs | 341 +---------------- .../src/Resource/Container/Container.cs | 63 +--- .../Resource/Container/ContainerCore.Items.cs | 1 + .../src/Resource/Container/ContainerCore.cs | 12 + .../Resource/Container/ContainerInlineCore.cs | 6 +- .../Resource/Container/ContainerInternal.cs | 2 + .../QueryResponses/ChangeFeedIteratorCore.cs | 13 +- ...geFeedPartitionKeyResultSetIteratorCore.cs | 2 +- .../QueryResponses/StandByFeedIteratorCore.cs | 13 +- .../Contracts/ContractTests.cs | 10 +- .../CosmosDiagnosticsTests.cs | 4 +- .../CosmosItemChangeFeedTests.cs | 20 +- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 96 ++--- .../Query/EndToEnd.cs | 5 +- .../ChangeFeedResultSetIteratorTests.cs | 8 +- .../StandByFeedContinuationTokenTests.cs | 20 +- 18 files changed, 458 insertions(+), 514 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs index dc8fbbe7f3..e5645f7120 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs @@ -18,23 +18,23 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore BuildResultSetIterator { FeedRangeInternal feedRange = new FeedRangePartitionKeyRange(partitionKeyRangeId); - ChangeFeedRequestOptions.StartFrom startFrom; + ChangeFeedStartFrom startFrom; if (continuationToken != null) { // For continuation based feed range we need to manufactor a new continuation token that has the partition key range id in it. - startFrom = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(continuationToken, feedRange); + startFrom = new ChangeFeedStartFromContinuationAndFeedRange(continuationToken, feedRange); } else if (startTime.HasValue) { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromTimeWithRange(startTime.Value, feedRange); + startFrom = ChangeFeedStartFrom.CreateFromTimeWithRange(startTime.Value, feedRange); } else if (startFromBeginning) { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange); + startFrom = ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange); } else { - startFrom = ChangeFeedRequestOptions.StartFrom.CreateFromNowWithRange(feedRange); + startFrom = ChangeFeedStartFrom.CreateFromNowWithRange(feedRange); } ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs new file mode 100644 index 0000000000..443d2df01c --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs @@ -0,0 +1,346 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Globalization; + using Microsoft.Azure.Documents; + + /// + /// Base class for where to start a ChangeFeed operation in . + /// + /// Use one of the static constructors to generate a StartFrom option. +#if PREVIEW + public +#else + internal +#endif + abstract class ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + internal protected ChangeFeedStartFrom() + { + // Internal so people can't derive from this type. + } + + internal abstract void Accept(StartFromVisitor visitor); + + internal abstract TResult Accept(StartFromVisitor visitor); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static ChangeFeedStartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static ChangeFeedStartFrom CreateFromNowWithRange(FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromNow(feedRangeInternal); + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time to start reading from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static ChangeFeedStartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time to start reading from. + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static ChangeFeedStartFrom CreateFromTimeWithRange(DateTime dateTime, FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromTime(dateTime, feedRangeInternal); + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. + /// + /// The continuation to resume from. + /// A that tells the ChangeFeed operation to start reading changes from a save point. + public static ChangeFeedStartFrom CreateFromContinuation(string continuation) => new ChangeFeedStartFromContinuation(continuation); + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static ChangeFeedStartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static ChangeFeedStartFrom CreateFromBeginningWithRange(FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromBeginning(feedRangeInternal); + } + } + + internal abstract class StartFromVisitor + { + public abstract void Visit(ChangeFeedStartFromNow startFromNow); + public abstract void Visit(ChangeFeedStartFromTime startFromTime); + public abstract void Visit(ChangeFeedStartFromContinuation startFromContinuation); + public abstract void Visit(ChangeFeedStartFromBeginning startFromBeginning); + public abstract void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); + } + + internal abstract class StartFromVisitor + { + public abstract TResult Visit(ChangeFeedStartFromNow startFromNow); + public abstract TResult Visit(ChangeFeedStartFromTime startFromTime); + public abstract TResult Visit(ChangeFeedStartFromContinuation startFromContinuation); + public abstract TResult Visit(ChangeFeedStartFromBeginning startFromBeginning); + public abstract TResult Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); + } + + internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor + { + private const string IfNoneMatchAllHeaderValue = "*"; + private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); + + private readonly RequestMessage requestMessage; + private readonly FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor; + + public PopulateStartFromRequestOptionVisitor(RequestMessage requestMessage) + { + this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); + this.feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); + } + + public override void Visit(ChangeFeedStartFromNow startFromNow) + { + this.requestMessage.Headers.IfNoneMatch = PopulateStartFromRequestOptionVisitor.IfNoneMatchAllHeaderValue; + + if (startFromNow.FeedRange != null) + { + startFromNow.FeedRange.Accept(this.feedRangeVisitor); + } + } + + public override void Visit(ChangeFeedStartFromTime startFromTime) + { + // Our current public contract for ChangeFeedProcessor uses DateTime.MinValue.ToUniversalTime as beginning. + // We need to add a special case here, otherwise it would send it as normal StartTime. + // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, + // it would break multi master Change Feed Processor users using Start From Beginning semantics. + // It's also an optimization, since the backend won't have to binary search for the value. + if (startFromTime.Time != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) + { + this.requestMessage.Headers.Add( + HttpConstants.HttpHeaders.IfModifiedSince, + startFromTime.Time.ToString("r", CultureInfo.InvariantCulture)); + } + + startFromTime.FeedRange.Accept(this.feedRangeVisitor); + } + + public override void Visit(ChangeFeedStartFromContinuation startFromContinuation) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + this.requestMessage.Headers.IfNoneMatch = startFromContinuation.Continuation; + } + + public override void Visit(ChangeFeedStartFromBeginning startFromBeginning) + { + // We don't need to set any headers to start from the beginning + + // Except for the feed range. + startFromBeginning.FeedRange.Accept(this.feedRangeVisitor); + } + + public override void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; + + startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); + } + } + + internal sealed class FeedRangeExtractor : StartFromVisitor + { + public static readonly FeedRangeExtractor Singleton = new FeedRangeExtractor(); + + private FeedRangeExtractor() + { + } + + public override FeedRange Visit(ChangeFeedStartFromNow startFromNow) => startFromNow.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromTime startFromTime) => startFromTime.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromContinuation startFromContinuation) + => throw new NotSupportedException($"{nameof(ChangeFeedStartFromContinuation)} does not have a feed range."); + + public override FeedRange Visit(ChangeFeedStartFromBeginning startFromBeginning) => startFromBeginning.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) => startFromContinuationAndFeedRange.FeedRange; + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + internal sealed class ChangeFeedStartFromNow : ChangeFeedStartFrom + { + /// + /// Intializes an instance of the class. + /// + /// The (optional) feed range to start from. + public ChangeFeedStartFromNow(FeedRangeInternal feedRange) + : base() + { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + internal sealed class ChangeFeedStartFromTime : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The time to start reading from. + /// The (optional) range to start from. + public ChangeFeedStartFromTime(DateTime time, FeedRangeInternal feedRange) + : base() + { + if (time.Kind != DateTimeKind.Utc) + { + throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); + } + + this.Time = time; + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the time the ChangeFeed operation should start reading from. + /// + public DateTime Time { get; } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. + /// + /// This class is used to temporarily store the fully serialized composite continuation token and needs to transformed into a . + internal sealed class ChangeFeedStartFromContinuation : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The continuation to resume from. + public ChangeFeedStartFromContinuation(string continuation) + : base() + { + if (string.IsNullOrWhiteSpace(continuation)) + { + throw new ArgumentOutOfRangeException($"{nameof(continuation)} must not be null, empty, or whitespace."); + } + + this.Continuation = continuation; + } + + /// + /// Gets the continuation to resume from. + /// + public string Continuation { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from the beginning of time. + /// + internal sealed class ChangeFeedStartFromBeginning : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The (optional) range to start from. + public ChangeFeedStartFromBeginning(FeedRangeInternal feedRange) + : base() + { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading from an LSN for a particular feed range. + /// + internal sealed class ChangeFeedStartFromContinuationAndFeedRange : ChangeFeedStartFrom + { + public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) + { + this.Etag = etag ?? throw new ArgumentNullException(nameof(etag)); + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + public string Etag { get; } + + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index 9bc80b9a8d..df7c28edbe 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -47,7 +47,7 @@ public int? MaxItemCount /// /// Only applies in the case where no FeedToken is provided or the FeedToken was never used in a previous iterator. /// - public StartFrom From { get; set; } = new StartFromNow(FeedRangeEpk.FullRange); + public ChangeFeedStartFrom From { get; set; } = new ChangeFeedStartFromNow(FeedRangeEpk.FullRange); /// /// Fill the CosmosRequestMessage headers with the set properties @@ -59,14 +59,6 @@ internal override void PopulateRequestOptions(RequestMessage request) base.PopulateRequestOptions(request); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); - if (this.From == null) - { - throw new InvalidOperationException($"{nameof(ChangeFeedRequestOptions)}.{nameof(ChangeFeedRequestOptions.StartFrom)} needs to be set to a value."); - } - - this.From.Accept(visitor); - if (this.MaxItemCount.HasValue) { request.Headers.Add( @@ -101,337 +93,6 @@ internal override void PopulateRequestOptions(RequestMessage request) set => throw new NotSupportedException($"{nameof(ChangeFeedRequestOptions)} does not use the {nameof(this.IfNoneMatchEtag)} property."); } - /// - /// Base class for where to start a ChangeFeed operation in . - /// - /// Use one of the static constructors to generate a StartFrom option. - public abstract class StartFrom - { - /// - /// Initializes an instance of the class. - /// - internal protected StartFrom() - { - // Internal so people can't derive from this type. - } - - internal abstract void Accept(StartFromVisitor visitor); - - internal abstract TResult Accept(StartFromVisitor visitor); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static StartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static StartFrom CreateFromNowWithRange(FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new StartFromNow(feedRangeInternal); - } - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - /// The time to start reading from. - /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static StartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - /// The time to start reading from. - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static StartFrom CreateFromTimeWithRange(DateTime dateTime, FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new StartFromTime(dateTime, feedRangeInternal); - } - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. - /// - /// The continuation to resume from. - /// A that tells the ChangeFeed operation to start reading changes from a save point. - public static StartFrom CreateFromContinuation(string continuation) => new StartFromContinuation(continuation); - - /// - /// Creates a that tells the ChangeFeed operation to start from the beginning of time. - /// - /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static StartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start from the beginning of time. - /// - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static StartFrom CreateFromBeginningWithRange(FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new StartFromBeginning(feedRangeInternal); - } - } - - internal abstract class StartFromVisitor - { - public abstract void Visit(StartFromNow startFromNow); - public abstract void Visit(StartFromTime startFromTime); - public abstract void Visit(StartFromContinuation startFromContinuation); - public abstract void Visit(StartFromBeginning startFromBeginning); - public abstract void Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange); - } - - internal abstract class StartFromVisitor - { - public abstract TResult Visit(StartFromNow startFromNow); - public abstract TResult Visit(StartFromTime startFromTime); - public abstract TResult Visit(StartFromContinuation startFromContinuation); - public abstract TResult Visit(StartFromBeginning startFromBeginning); - public abstract TResult Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange); - } - - internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor - { - private const string IfNoneMatchAllHeaderValue = "*"; - private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); - - private readonly RequestMessage requestMessage; - private readonly FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor; - - public PopulateStartFromRequestOptionVisitor(RequestMessage requestMessage) - { - this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); - this.feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); - } - - public override void Visit(StartFromNow startFromNow) - { - this.requestMessage.Headers.IfNoneMatch = PopulateStartFromRequestOptionVisitor.IfNoneMatchAllHeaderValue; - - if (startFromNow.FeedRange != null) - { - startFromNow.FeedRange.Accept(this.feedRangeVisitor); - } - } - - public override void Visit(StartFromTime startFromTime) - { - // Our current public contract for ChangeFeedProcessor uses DateTime.MinValue.ToUniversalTime as beginning. - // We need to add a special case here, otherwise it would send it as normal StartTime. - // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, - // it would break multi master Change Feed Processor users using Start From Beginning semantics. - // It's also an optimization, since the backend won't have to binary search for the value. - if (startFromTime.Time != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) - { - this.requestMessage.Headers.Add( - HttpConstants.HttpHeaders.IfModifiedSince, - startFromTime.Time.ToString("r", CultureInfo.InvariantCulture)); - } - - startFromTime.FeedRange.Accept(this.feedRangeVisitor); - } - - public override void Visit(StartFromContinuation startFromContinuation) - { - // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - this.requestMessage.Headers.IfNoneMatch = startFromContinuation.Continuation; - } - - public override void Visit(StartFromBeginning startFromBeginning) - { - // We don't need to set any headers to start from the beginning - - // Except for the feed range. - startFromBeginning.FeedRange.Accept(this.feedRangeVisitor); - } - - public override void Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange) - { - // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; - - startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); - } - } - - internal sealed class FeedRangeExtractor : StartFromVisitor - { - public static readonly FeedRangeExtractor Singleton = new FeedRangeExtractor(); - - private FeedRangeExtractor() - { - } - - public override FeedRange Visit(StartFromNow startFromNow) => startFromNow.FeedRange; - - public override FeedRange Visit(StartFromTime startFromTime) => startFromTime.FeedRange; - - public override FeedRange Visit(StartFromContinuation startFromContinuation) - => throw new NotSupportedException($"{nameof(StartFromContinuation)} does not have a feed range."); - - public override FeedRange Visit(StartFromBeginning startFromBeginning) => startFromBeginning.FeedRange; - - public override FeedRange Visit(StartFromContinuationAndFeedRange startFromContinuationAndFeedRange) => startFromContinuationAndFeedRange.FeedRange; - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - internal sealed class StartFromNow : StartFrom - { - /// - /// Intializes an instance of the class. - /// - /// The (optional) feed range to start from. - public StartFromNow(FeedRangeInternal feedRange) - : base() - { - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - internal sealed class StartFromTime : StartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The time to start reading from. - /// The (optional) range to start from. - public StartFromTime(DateTime time, FeedRangeInternal feedRange) - : base() - { - if (time.Kind != DateTimeKind.Utc) - { - throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); - } - - this.Time = time; - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the time the ChangeFeed operation should start reading from. - /// - public DateTime Time { get; } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. - /// - /// This class is used to temporarily store the fully serialized composite continuation token and needs to transformed into a . - internal sealed class StartFromContinuation : StartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The continuation to resume from. - public StartFromContinuation(string continuation) - : base() - { - if (string.IsNullOrWhiteSpace(continuation)) - { - throw new ArgumentOutOfRangeException($"{nameof(continuation)} must not be null, empty, or whitespace."); - } - - this.Continuation = continuation; - } - - /// - /// Gets the continuation to resume from. - /// - public string Continuation { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from the beginning of time. - /// - internal sealed class StartFromBeginning : StartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The (optional) range to start from. - public StartFromBeginning(FeedRangeInternal feedRange) - : base() - { - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading from an LSN for a particular feed range. - /// - internal sealed class StartFromContinuationAndFeedRange : StartFrom - { - public StartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) - { - this.Etag = etag ?? throw new ArgumentNullException(nameof(etag)); - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - public string Etag { get; } - - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - internal ChangeFeedRequestOptions Clone() { return new ChangeFeedRequestOptions() diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index cae8a40050..2cdcccaa3a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1176,6 +1176,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( /// /// This method creates an iterator to consume a Change Feed. /// + /// Where to start the changefeed from. /// (Optional) The options for the Change Feed consumption. /// /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api @@ -1211,42 +1212,13 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( /// /// An iterator to go through the Change Feed. public abstract FeedIterator GetChangeFeedStreamIterator( - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// This method creates an iterator to consume the Change Feed for a Partition Key value. - /// - /// A to read the Change Feed for. - /// (Optional) The options for the Change Feed consumption. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// - /// - /// - /// An iterator to go through the Change Feed for a particular Partition Key. - public abstract FeedIterator GetChangeFeedStreamIterator( - PartitionKey partitionKey, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null); /// /// This method creates an iterator to consume a Change Feed. /// + /// Where to start the changefeed from. /// (Optional) The options for the Change Feed consumption. /// /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api @@ -1278,34 +1250,7 @@ public abstract FeedIterator GetChangeFeedStreamIterator( /// /// An iterator to go through the Change Feed. public abstract FeedIterator GetChangeFeedIterator( - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// This method creates an iterator to consume the Change Feed for a Partition Key value. - /// - /// A to read the Change Feed for. - /// (Optional) The options for the Change Feed consumption. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// feedIterator = this.Container.GetChangeFeedIterator(new PartitionKey("myPartitionKey"))) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// FeedResponse response = await feedIterator.ReadNextAsync(); - /// foreach (var item in response) - /// { - /// Console.WriteLine(item); - /// } - /// } - /// } - /// ]]> - /// - /// - /// An iterator to go through the Change Feed for a Partition Key. - public abstract FeedIterator GetChangeFeedIterator( - PartitionKey partitionKey, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null); /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 681d806e0c..69120ef201 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -551,6 +551,7 @@ internal override FeedIterator GetStandByFeedIterator( return new StandByFeedIteratorCore( clientContext: this.ClientContext, container: this, + changeFeedStartFrom: ChangeFeedStartFrom.CreateFromBeginning(), options: cosmosQueryRequestOptions); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index a86d7f6650..2852e440d7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -251,16 +251,28 @@ public async Task> GetFeedRangesAsync( } public override FeedIterator GetChangeFeedStreamIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null) { + if (changeFeedStartFrom == null) + { + throw new ArgumentNullException(nameof(changeFeedStartFrom)); + } + return new ChangeFeedIteratorCore( container: this, changeFeedRequestOptions: changeFeedRequestOptions); } public override FeedIterator GetChangeFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null) { + if (changeFeedStartFrom == null) + { + throw new ArgumentNullException(nameof(changeFeedStartFrom)); + } + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( container: this, changeFeedRequestOptions: changeFeedRequestOptions); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index fdc3edad50..b958ea0d87 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -373,15 +373,17 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti } public override FeedIterator GetChangeFeedStreamIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return base.GetChangeFeedStreamIterator(changeFeedRequestOptions); + return base.GetChangeFeedStreamIterator(changeFeedStartFrom, changeFeedRequestOptions); } public override FeedIterator GetChangeFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return base.GetChangeFeedIterator(changeFeedRequestOptions); + return base.GetChangeFeedIterator(changeFeedStartFrom, changeFeedRequestOptions); } public override Task> GetPartitionKeyRangesAsync( diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index a147e868fa..5b3670b327 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -111,9 +111,11 @@ public abstract Task> PatchItemAsync( public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken = default(CancellationToken)); public abstract FeedIterator GetChangeFeedStreamIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null); public abstract FeedIterator GetChangeFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions = null); public abstract Task> GetPartitionKeyRangesAsync( diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index 8ecb2dcff8..5f6957e164 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; - using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -43,7 +42,7 @@ public ChangeFeedIteratorCore( }); this.hasMoreResults = true; - if (this.changeFeedOptions?.From is ChangeFeedRequestOptions.StartFromContinuation startFromContinuation) + if (this.changeFeedOptions?.From is ChangeFeedStartFromContinuation startFromContinuation) { if (!FeedRangeContinuation.TryParse(startFromContinuation.Continuation, out FeedRangeContinuation feedRangeContinuation)) { @@ -54,7 +53,7 @@ public ChangeFeedIteratorCore( FeedRange feedRange = feedRangeContinuation.GetFeedRange(); string etag = feedRangeContinuation.GetContinuation(); - this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } } @@ -72,7 +71,7 @@ public override async Task ReadNextAsync(CancellationToken canc { diagnostics.AddDiagnosticsInternal( new FeedRangeStatistics( - this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton))); + this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton))); if (!this.lazyContainerRid.ValueInitialized) { using (diagnostics.CreateScope("InitializeContainerResourceId")) @@ -129,7 +128,7 @@ private async Task ReadNextInternalAsync( string etag = this.FeedRangeContinuation.GetContinuation(); if (etag != null) { - FeedRange feedRange = this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton); + FeedRange feedRange = this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton); if ((feedRange == null) || feedRange is FeedRangeEpk) { // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId @@ -137,7 +136,7 @@ private async Task ReadNextInternalAsync( feedRange = this.FeedRangeContinuation.GetFeedRange(); } - this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( @@ -213,7 +212,7 @@ private async Task InitializeFeedContinuationAsync(CancellationToken cancellatio { FeedRangePartitionKeyRangeExtractor feedRangePartitionKeyRangeExtractor = new FeedRangePartitionKeyRangeExtractor(this.container); - FeedRange feedRange = this.changeFeedOptions.From.Accept(ChangeFeedRequestOptions.FeedRangeExtractor.Singleton); + FeedRange feedRange = this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton); IReadOnlyList> ranges = await ((FeedRangeInternal)feedRange).AcceptAsync( feedRangePartitionKeyRangeExtractor, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 8edcf583e4..4c99891ddf 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -45,7 +45,7 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( // Change Feed read uses Etag for continuation if (this.continuationToken != null) { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(this.continuationToken); + this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromContinuation(this.continuationToken); } ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs index e9248056bb..4073f63c1f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs @@ -28,17 +28,20 @@ internal class StandByFeedIteratorCore : FeedIteratorInternal private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; + private readonly ChangeFeedStartFrom changeFeedStartFrom; private string containerRid; internal StandByFeedIteratorCore( CosmosClientContext clientContext, ContainerCore container, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions options) { if (container == null) throw new ArgumentNullException(nameof(container)); this.clientContext = clientContext; this.container = container; + this.changeFeedStartFrom = changeFeedStartFrom ?? throw new ArgumentNullException(nameof(changeFeedStartFrom)); this.changeFeedOptions = options; } @@ -95,7 +98,7 @@ internal StandByFeedIteratorCore( PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); this.containerRid = await this.container.GetRIDAsync(cancellationToken); - if (this.changeFeedOptions?.From is ChangeFeedRequestOptions.StartFromContinuation startFromContinuation) + if (this.changeFeedOptions?.From is ChangeFeedStartFromContinuation startFromContinuation) { this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync( this.containerRid, @@ -105,11 +108,11 @@ internal StandByFeedIteratorCore( if (token.Token != null) { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token.Token); + this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromContinuation(token.Token); } else { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(); + this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromBeginning(); } } else @@ -125,11 +128,11 @@ internal StandByFeedIteratorCore( FeedRange feedRange = new FeedRangePartitionKeyRange(rangeId); if (currentRangeToken.Token != null) { - this.changeFeedOptions.From = new ChangeFeedRequestOptions.StartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange); + this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange); } else { - this.changeFeedOptions.From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange); + this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange); } ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index 148663f4c7..7174b84476 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -156,10 +156,11 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() IEnumerable pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange), MaxItemCount = 1 }; - ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator( + changeFeedStartFrom: ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange), + changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) { @@ -195,10 +196,11 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() { ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), MaxItemCount = 100 }; - ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator( + changeFeedStartFrom: ChangeFeedStartFrom.CreateFromContinuation(continuation), + changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs index ca76cc8563..eb93f8c81b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -472,7 +472,9 @@ public async Task ChangeFeedDiagnostics(bool disableDiagnostics) await Task.WhenAll(createItemsTasks); ChangeFeedRequestOptions requestOptions = disableDiagnostics ? ChangeFeedRequestOptionDisableDiagnostic : null; - FeedIterator changeFeedIterator = ((ContainerCore)(container as ContainerInlineCore)).GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions); + FeedIterator changeFeedIterator = ((ContainerCore)(container as ContainerInlineCore)).GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginning(), + changeFeedRequestOptions: requestOptions); while (changeFeedIterator.HasMoreResults) { using (ResponseMessage response = await changeFeedIterator.ReadNextAsync()) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs index 9d2d637788..d6d333107e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs @@ -68,7 +68,7 @@ public async Task StandByFeedIterator() FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( requestOptions: new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }); while (feedIterator.HasMoreResults) @@ -100,7 +100,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) itemsCore.GetStandByFeedIterator( new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(lastcontinuation), + From = ChangeFeedStartFrom.CreateFromContinuation(lastcontinuation), }); while (setIteratorNew.HasMoreResults) @@ -192,7 +192,7 @@ public async Task StandByFeedIterator_WithInexistentRange() itemsCore.GetStandByFeedIterator( requestOptions: new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(corruptedTokenSerialized), + From = ChangeFeedStartFrom.CreateFromContinuation(corruptedTokenSerialized), }); _ = await setIteratorNew.ReadNextAsync(this.cancellationToken); @@ -211,7 +211,7 @@ public async Task StandByFeedIterator_WithMaxItemCount() requestOptions: new ChangeFeedRequestOptions() { MaxItemCount = 1, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }); while (feedIterator.HasMoreResults) @@ -250,7 +250,7 @@ public async Task StandByFeedIterator_NoFetchNext() { ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }; FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( @@ -299,7 +299,7 @@ public async Task StandByFeedIterator_BreathFirst() FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( requestOptions: new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning() + From = ChangeFeedStartFrom.CreateFromBeginning() }); while (feedIterator.HasMoreResults) { @@ -328,10 +328,10 @@ public async Task StandByFeedIterator_VerifyRefreshIsCalledOnSplit() { CosmosChangeFeedResultSetIteratorCoreMock iterator = new CosmosChangeFeedResultSetIteratorCoreMock( (ContainerCore)this.Container, + ChangeFeedStartFrom.CreateFromBeginning(), new ChangeFeedRequestOptions() { MaxItemCount = 100, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }); using (ResponseMessage responseMessage = await iterator.ReadNextAsync(this.cancellationToken)) @@ -368,7 +368,7 @@ public async Task GetChangeFeedTokensAsync_AllowsParallelProcessing() itemsCore.GetStandByFeedIterator( requestOptions: new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token), + From = ChangeFeedStartFrom.CreateFromContinuation(token), }); while (true) { @@ -417,7 +417,7 @@ public async Task GetChangeFeedTokensAsync_DrainFromJustOnePartition() itemsCore.GetStandByFeedIterator( requestOptions: new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(token), + From = ChangeFeedStartFrom.CreateFromContinuation(token), }); while (iteratorForToken.HasMoreResults) { @@ -486,9 +486,11 @@ private class CosmosChangeFeedResultSetIteratorCoreMock : StandByFeedIteratorCor internal CosmosChangeFeedResultSetIteratorCoreMock( ContainerCore container, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions options) : base( clientContext: container.ClientContext, container: container, + changeFeedStartFrom: changeFeedStartFrom, options: options) { List compositeContinuationTokens = new List() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index 83a7d4d9c9..8bb5f32897 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -62,11 +62,7 @@ public async Task ChangeFeedIteratorCore_ReadAll() await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - changeFeedRequestOptions: - new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), - }) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -90,10 +86,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), - }) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.CreateFromContinuation(continuation)) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -126,10 +119,7 @@ public async Task ChangeFeedIteratorCore_StartTime() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; FeedIterator feedIterator = itemsCore.GetChangeFeedStreamIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromTime(now), - }); + ChangeFeedStartFrom.CreateFromTime(now)); while (feedIterator.HasMoreResults) { using (ResponseMessage responseMessage = @@ -172,10 +162,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll() ContainerInternal itemsCore = this.Container; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead))), - }) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead)))) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -207,10 +194,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) } ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), - }) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.CreateFromContinuation(continuation)) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -258,10 +242,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() ContainerInternal itemsCore = this.Container; FeedIterator feedIterator = itemsCore.GetChangeFeedIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead))), - }); + ChangeFeedStartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead)))); string continuation = null; while (feedIterator.HasMoreResults) { @@ -286,10 +267,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() } FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), - }); + ChangeFeedStartFrom.CreateFromContinuation(continuation)); while (setIteratorNew.HasMoreResults) { @@ -317,10 +295,7 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), - }); + FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.CreateFromBeginning()); string continuation = null; while (feedIterator.HasMoreResults) { @@ -335,10 +310,7 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); - FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), - }); + FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.CreateFromContinuation(continuation)); while (setIteratorNew.HasMoreResults) { @@ -361,7 +333,8 @@ public async Task ChangeFeedIteratorCore_EmptyBeginning() bool createdDocuments = false; ContainerInternal itemsCore = this.Container; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator() as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults || (createdDocuments && totalCount == 0)) @@ -397,11 +370,12 @@ public async Task ChangeFeedIteratorCore_WithMaxItemCount() { await this.CreateRandomItems(this.Container, 2, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - MaxItemCount = 1, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), - }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginning(), + changeFeedRequestOptions: new ChangeFeedRequestOptions() + { + MaxItemCount = 1, + }) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) { @@ -446,18 +420,20 @@ public async Task ChangeFeedIteratorCore_NoFetchNext() { requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }; } else { requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(continuation), + From = ChangeFeedStartFrom.CreateFromContinuation(continuation), }; } - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginning(), + changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { if (responseMessage.IsSuccessStatusCode) @@ -501,11 +477,12 @@ public async Task ChangeFeedIteratorCore_BreathFirst() List previousToken = null; await this.CreateRandomItems(this.LargerContainer, expected, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - MaxItemCount = 1, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), - }) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginning(), + new ChangeFeedRequestOptions() + { + MaxItemCount = 1, + }) as ChangeFeedIteratorCore; while (true) { using (ResponseMessage responseMessage = @@ -550,10 +527,8 @@ public async Task GetFeedRangesAsync_AllowsParallelProcessing() { int count = 0; ChangeFeedIteratorCore iteratorForToken = - itemsCore.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(token), - }) as ChangeFeedIteratorCore; + itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromBeginningWithRange(token)) as ChangeFeedIteratorCore; while (true) { using (ResponseMessage responseMessage = @@ -589,15 +564,10 @@ public async Task CannotMixTokensFromOtherContainers() { IReadOnlyList tokens = await this.LargerContainer.GetFeedRangesAsync(); FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator( - new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(tokens[0]), - }); + ChangeFeedStartFrom.CreateFromBeginningWithRange(tokens[0])); ResponseMessage responseMessage = await iterator.ReadNextAsync(); - iterator = this.Container.GetChangeFeedStreamIterator(new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation(responseMessage.ContinuationToken), - }); + iterator = this.Container.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.CreateFromContinuation(responseMessage.ContinuationToken)); responseMessage = await iterator.ReadNextAsync(); Assert.IsNotNull(responseMessage.CosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs index 7d349a37a9..465d9f3291 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs @@ -120,10 +120,7 @@ public async Task ChangeFeedBaselineAsync() { ChangeFeedIteratorCore feedIterator = ((ContainerCore)this.container) .GetChangeFeedStreamIterator( - changeFeedRequestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), - }) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 825f449408..03a69badc4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -66,10 +66,10 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + ChangeFeedStartFrom.CreateFromBeginning(), new ChangeFeedRequestOptions() { MaxItemCount = 10, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -128,10 +128,10 @@ public async Task ShouldContinueUntilResponseOk() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + ChangeFeedStartFrom.CreateFromBeginning(), new ChangeFeedRequestOptions() { MaxItemCount = 10, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -193,10 +193,10 @@ public async Task ShouldReturnNotModifiedAfterCyclingOnAllRanges() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + ChangeFeedStartFrom.CreateFromBeginning(), new ChangeFeedRequestOptions() { MaxItemCount = 10, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -253,10 +253,10 @@ public async Task ShouldReturnNotModifiedOnSingleRange() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), + ChangeFeedStartFrom.CreateFromBeginning(), new ChangeFeedRequestOptions() { MaxItemCount = 10, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index 3390924e5e..339fcabd31 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -226,7 +226,7 @@ public void ChangeFeedRequestOptions_ContinuationIsSet() RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromContinuation("something"), + From = ChangeFeedStartFrom.CreateFromContinuation("something"), }; requestOptions.PopulateRequestOptions(request); @@ -240,7 +240,7 @@ public void ChangeFeedRequestOptions_StartFromNow() RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromNow(), + From = ChangeFeedStartFrom.CreateFromNow(), }; requestOptions.PopulateRequestOptions(request); @@ -255,7 +255,7 @@ public void ChangeFeedRequestOptions_StartFromBeginning() RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }; requestOptions.PopulateRequestOptions(request); @@ -285,7 +285,7 @@ public void ChangeFeedRequestOptions_MaxItemSizeIsSet() ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { MaxItemCount = 10, - From = ChangeFeedRequestOptions.StartFrom.CreateFromBeginning(), + From = ChangeFeedStartFrom.CreateFromBeginning(), }; requestOptions.PopulateRequestOptions(request); @@ -300,7 +300,7 @@ public void ChangeFeedRequestOptions_AddsStartTime() RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - From = ChangeFeedRequestOptions.StartFrom.CreateFromTime(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)), + From = ChangeFeedStartFrom.CreateFromTime(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)), }; requestOptions.PopulateRequestOptions(request); @@ -314,14 +314,14 @@ public void ChangeFeedRequestOptions_AddsStartTime() public void ChangeFeedRequestOptions_AddsFeedRange() { FeedRange feedRange = new FeedRangePartitionKeyRange("randomPK"); - ChangeFeedRequestOptions.StartFrom[] froms = new ChangeFeedRequestOptions.StartFrom[] + ChangeFeedStartFrom[] froms = new ChangeFeedStartFrom[] { - ChangeFeedRequestOptions.StartFrom.CreateFromBeginningWithRange(feedRange), - ChangeFeedRequestOptions.StartFrom.CreateFromNowWithRange(feedRange), - ChangeFeedRequestOptions.StartFrom.CreateFromTimeWithRange(DateTime.MinValue.ToUniversalTime(), feedRange) + ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange), + ChangeFeedStartFrom.CreateFromNowWithRange(feedRange), + ChangeFeedStartFrom.CreateFromTimeWithRange(DateTime.MinValue.ToUniversalTime(), feedRange) }; - foreach (ChangeFeedRequestOptions.StartFrom from in froms) + foreach (ChangeFeedStartFrom from in froms) { RequestMessage request = new RequestMessage(); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() From e024d63cc8be3a9ffd9b182206d3be9768c88e0f Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Thu, 6 Aug 2020 18:17:55 -0700 Subject: [PATCH 07/19] added feedback from API review --- .../Utils/ResultSetIteratorUtils.cs | 10 ++-- .../src/ChangeFeedStartFrom.cs | 22 ++++---- .../ChangeFeedRequestOptions.cs | 28 ++++------- .../Resource/Container/ContainerCore.Items.cs | 3 +- .../src/Resource/Container/ContainerCore.cs | 2 + .../Resource/Container/ContainerInternal.cs | 1 + .../QueryResponses/ChangeFeedIteratorCore.cs | 21 +++++--- ...geFeedPartitionKeyResultSetIteratorCore.cs | 11 +++- .../QueryResponses/StandByFeedIteratorCore.cs | 18 ++++--- .../Contracts/ContractTests.cs | 8 +-- .../CosmosDiagnosticsTests.cs | 2 +- .../CosmosItemChangeFeedTests.cs | 48 +++++------------- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 50 ++++++++----------- .../Query/EndToEnd.cs | 2 +- .../ChangeFeedResultSetIteratorTests.cs | 16 +++--- .../FeedRange/ChangeFeedIteratorCoreTests.cs | 22 ++++++-- .../StandByFeedContinuationTokenTests.cs | 48 ++++++------------ 17 files changed, 146 insertions(+), 166 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs index e5645f7120..ce5cb03d11 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/ResultSetIteratorUtils.cs @@ -26,26 +26,26 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore BuildResultSetIterator } else if (startTime.HasValue) { - startFrom = ChangeFeedStartFrom.CreateFromTimeWithRange(startTime.Value, feedRange); + startFrom = ChangeFeedStartFrom.Time(startTime.Value, feedRange); } else if (startFromBeginning) { - startFrom = ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange); + startFrom = ChangeFeedStartFrom.Beginning(feedRange); } else { - startFrom = ChangeFeedStartFrom.CreateFromNowWithRange(feedRange); + startFrom = ChangeFeedStartFrom.Now(feedRange); } ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - MaxItemCount = maxItemCount, - From = startFrom, + PageSizeHint = maxItemCount, }; return new ChangeFeedPartitionKeyResultSetIteratorCore( clientContext: container.ClientContext, container: container, + changeFeedStartFrom: startFrom, options: requestOptions); } } diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs index 443d2df01c..dd0297144a 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs @@ -35,14 +35,14 @@ internal protected ChangeFeedStartFrom() /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. /// /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static ChangeFeedStartFrom CreateFromNow() => CreateFromNowWithRange(FeedRangeEpk.FullRange); + public static ChangeFeedStartFrom Now() => Now(FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. /// /// The range to start from. /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static ChangeFeedStartFrom CreateFromNowWithRange(FeedRange feedRange) + public static ChangeFeedStartFrom Now(FeedRange feedRange) { if (!(feedRange is FeedRangeInternal feedRangeInternal)) { @@ -57,7 +57,7 @@ public static ChangeFeedStartFrom CreateFromNowWithRange(FeedRange feedRange) /// /// The time to start reading from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom CreateFromTime(DateTime dateTime) => CreateFromTimeWithRange(dateTime, FeedRangeEpk.FullRange); + public static ChangeFeedStartFrom Time(DateTime dateTime) => Time(dateTime, FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. @@ -65,7 +65,7 @@ public static ChangeFeedStartFrom CreateFromNowWithRange(FeedRange feedRange) /// The time to start reading from. /// The range to start from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom CreateFromTimeWithRange(DateTime dateTime, FeedRange feedRange) + public static ChangeFeedStartFrom Time(DateTime dateTime, FeedRange feedRange) { if (!(feedRange is FeedRangeInternal feedRangeInternal)) { @@ -80,20 +80,20 @@ public static ChangeFeedStartFrom CreateFromTimeWithRange(DateTime dateTime, Fee /// /// The continuation to resume from. /// A that tells the ChangeFeed operation to start reading changes from a save point. - public static ChangeFeedStartFrom CreateFromContinuation(string continuation) => new ChangeFeedStartFromContinuation(continuation); + public static ChangeFeedStartFrom ContinuationToken(string continuation) => new ChangeFeedStartFromContinuation(continuation); /// /// Creates a that tells the ChangeFeed operation to start from the beginning of time. /// /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static ChangeFeedStartFrom CreateFromBeginning() => CreateFromBeginningWithRange(FeedRangeEpk.FullRange); + public static ChangeFeedStartFrom Beginning() => Beginning(FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start from the beginning of time. /// /// The range to start from. /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static ChangeFeedStartFrom CreateFromBeginningWithRange(FeedRange feedRange) + public static ChangeFeedStartFrom Beginning(FeedRange feedRange) { if (!(feedRange is FeedRangeInternal feedRangeInternal)) { @@ -153,11 +153,11 @@ public override void Visit(ChangeFeedStartFromTime startFromTime) // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, // it would break multi master Change Feed Processor users using Start From Beginning semantics. // It's also an optimization, since the backend won't have to binary search for the value. - if (startFromTime.Time != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) + if (startFromTime.StartTime != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) { this.requestMessage.Headers.Add( HttpConstants.HttpHeaders.IfModifiedSince, - startFromTime.Time.ToString("r", CultureInfo.InvariantCulture)); + startFromTime.StartTime.ToString("r", CultureInfo.InvariantCulture)); } startFromTime.FeedRange.Accept(this.feedRangeVisitor); @@ -249,14 +249,14 @@ public ChangeFeedStartFromTime(DateTime time, FeedRangeInternal feedRange) throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); } - this.Time = time; + this.StartTime = time; this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } /// /// Gets the time the ChangeFeed operation should start reading from. /// - public DateTime Time { get; } + public DateTime StartTime { get; } /// /// Gets the (optional) range to start from. diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs index df7c28edbe..c3c7db7d9a 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/ChangeFeedRequestOptions.cs @@ -19,36 +19,29 @@ namespace Microsoft.Azure.Cosmos #endif sealed class ChangeFeedRequestOptions : RequestOptions { - private int? maxItemCount; + private int? pageSizeHint; /// /// Gets or sets the maximum number of items to be returned in the enumeration operation in the Azure Cosmos DB service. /// /// /// The maximum number of items to be returned in the enumeration operation. - /// - public int? MaxItemCount + /// + /// This is just a hint to the server which can return less items per page. + public int? PageSizeHint { - get => this.maxItemCount; + get => this.pageSizeHint; set { if (value.HasValue && (value.Value <= 0)) { - throw new ArgumentOutOfRangeException($"{nameof(this.MaxItemCount)} must be a positive value."); + throw new ArgumentOutOfRangeException($"{nameof(this.PageSizeHint)} must be a positive value."); } - this.maxItemCount = value; + this.pageSizeHint = value; } } - /// - /// Gets or sets where the ChangeFeed operation should start from. If not set then the ChangeFeed operation will start from now. - /// - /// - /// Only applies in the case where no FeedToken is provided or the FeedToken was never used in a previous iterator. - /// - public ChangeFeedStartFrom From { get; set; } = new ChangeFeedStartFromNow(FeedRangeEpk.FullRange); - /// /// Fill the CosmosRequestMessage headers with the set properties /// @@ -59,11 +52,11 @@ internal override void PopulateRequestOptions(RequestMessage request) base.PopulateRequestOptions(request); - if (this.MaxItemCount.HasValue) + if (this.PageSizeHint.HasValue) { request.Headers.Add( HttpConstants.HttpHeaders.PageSize, - this.MaxItemCount.Value.ToString(CultureInfo.InvariantCulture)); + this.PageSizeHint.Value.ToString(CultureInfo.InvariantCulture)); } request.Headers.Add( @@ -97,8 +90,7 @@ internal ChangeFeedRequestOptions Clone() { return new ChangeFeedRequestOptions() { - MaxItemCount = this.maxItemCount, - From = this.From, + PageSizeHint = this.pageSizeHint, }; } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 69120ef201..43ab14a526 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -544,6 +544,7 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti } internal override FeedIterator GetStandByFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions requestOptions = null) { ChangeFeedRequestOptions cosmosQueryRequestOptions = requestOptions as ChangeFeedRequestOptions ?? new ChangeFeedRequestOptions(); @@ -551,7 +552,7 @@ internal override FeedIterator GetStandByFeedIterator( return new StandByFeedIteratorCore( clientContext: this.ClientContext, container: this, - changeFeedStartFrom: ChangeFeedStartFrom.CreateFromBeginning(), + changeFeedStartFrom: changeFeedStartFrom, options: cosmosQueryRequestOptions); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 2852e440d7..f264f13973 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -261,6 +261,7 @@ public override FeedIterator GetChangeFeedStreamIterator( return new ChangeFeedIteratorCore( container: this, + changeFeedStartFrom: changeFeedStartFrom, changeFeedRequestOptions: changeFeedRequestOptions); } @@ -275,6 +276,7 @@ public override FeedIterator GetChangeFeedIterator( ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( container: this, + changeFeedStartFrom: changeFeedStartFrom, changeFeedRequestOptions: changeFeedRequestOptions); return new FeedIteratorCore(changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index 5b3670b327..b21102f140 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -56,6 +56,7 @@ public abstract Task TryExecuteQueryAsync( CancellationToken cancellationToken = default); internal abstract FeedIterator GetStandByFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions requestOptions = default); public abstract FeedIteratorInternal GetItemQueryStreamIteratorInternal( diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index 5f6957e164..f8c7bd9832 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -25,12 +25,14 @@ internal sealed class ChangeFeedIteratorCore : FeedIteratorInternal private readonly CosmosClientContext clientContext; private readonly ChangeFeedRequestOptions changeFeedOptions; private readonly AsyncLazy> lazyContainerRid; + private ChangeFeedStartFrom changeFeedStartFrom; private bool hasMoreResults; private FeedRangeContinuation FeedRangeContinuation; public ChangeFeedIteratorCore( ContainerInternal container, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions changeFeedRequestOptions) { this.container = container ?? throw new ArgumentNullException(nameof(container)); @@ -42,7 +44,8 @@ public ChangeFeedIteratorCore( }); this.hasMoreResults = true; - if (this.changeFeedOptions?.From is ChangeFeedStartFromContinuation startFromContinuation) + this.changeFeedStartFrom = changeFeedStartFrom; + if (this.changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation) { if (!FeedRangeContinuation.TryParse(startFromContinuation.Continuation, out FeedRangeContinuation feedRangeContinuation)) { @@ -53,7 +56,7 @@ public ChangeFeedIteratorCore( FeedRange feedRange = feedRangeContinuation.GetFeedRange(); string etag = feedRangeContinuation.GetContinuation(); - this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } } @@ -71,7 +74,7 @@ public override async Task ReadNextAsync(CancellationToken canc { diagnostics.AddDiagnosticsInternal( new FeedRangeStatistics( - this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton))); + this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton))); if (!this.lazyContainerRid.ValueInitialized) { using (diagnostics.CreateScope("InitializeContainerResourceId")) @@ -128,7 +131,7 @@ private async Task ReadNextInternalAsync( string etag = this.FeedRangeContinuation.GetContinuation(); if (etag != null) { - FeedRange feedRange = this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton); + FeedRange feedRange = this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); if ((feedRange == null) || feedRange is FeedRangeEpk) { // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId @@ -136,7 +139,7 @@ private async Task ReadNextInternalAsync( feedRange = this.FeedRangeContinuation.GetFeedRange(); } - this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); } ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( @@ -145,7 +148,11 @@ private async Task ReadNextInternalAsync( operationType: OperationType.ReadFeed, requestOptions: this.changeFeedOptions, cosmosContainerCore: this.container, - requestEnricher: default, + requestEnricher: (request) => + { + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + this.changeFeedStartFrom.Accept(visitor); + }, partitionKey: default, streamPayload: default, diagnosticsContext: diagnosticsScope, @@ -212,7 +219,7 @@ private async Task InitializeFeedContinuationAsync(CancellationToken cancellatio { FeedRangePartitionKeyRangeExtractor feedRangePartitionKeyRangeExtractor = new FeedRangePartitionKeyRangeExtractor(this.container); - FeedRange feedRange = this.changeFeedOptions.From.Accept(FeedRangeExtractor.Singleton); + FeedRange feedRange = this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); IReadOnlyList> ranges = await ((FeedRangeInternal)feedRange).AcceptAsync( feedRangePartitionKeyRangeExtractor, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 4c99891ddf..f8f294a72b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -18,16 +18,19 @@ internal sealed class ChangeFeedPartitionKeyResultSetIteratorCore : FeedIterator private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; private readonly ChangeFeedRequestOptions changeFeedOptions; + private ChangeFeedStartFrom changeFeedStartFrom; private bool hasMoreResultsInternal; private string continuationToken; public ChangeFeedPartitionKeyResultSetIteratorCore( CosmosClientContext clientContext, ContainerInternal container, + ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedRequestOptions options) { this.clientContext = clientContext ?? throw new ArgumentNullException(nameof(clientContext)); this.container = container ?? throw new ArgumentNullException(nameof(container)); + this.changeFeedStartFrom = changeFeedStartFrom; this.changeFeedOptions = options; } @@ -45,7 +48,7 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( // Change Feed read uses Etag for continuation if (this.continuationToken != null) { - this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromContinuation(this.continuationToken); + this.changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(this.continuationToken); } ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( @@ -54,7 +57,11 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( resourceType: Documents.ResourceType.Document, operationType: Documents.OperationType.ReadFeed, requestOptions: this.changeFeedOptions, - requestEnricher: default, + requestEnricher: (requestMessage) => + { + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(requestMessage); + this.changeFeedStartFrom.Accept(visitor); + }, partitionKey: default, streamPayload: default, diagnosticsContext: default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs index 4073f63c1f..db6313ab8a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs @@ -28,7 +28,7 @@ internal class StandByFeedIteratorCore : FeedIteratorInternal private readonly CosmosClientContext clientContext; private readonly ContainerInternal container; - private readonly ChangeFeedStartFrom changeFeedStartFrom; + private ChangeFeedStartFrom changeFeedStartFrom; private string containerRid; internal StandByFeedIteratorCore( @@ -98,7 +98,7 @@ internal StandByFeedIteratorCore( PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(); this.containerRid = await this.container.GetRIDAsync(cancellationToken); - if (this.changeFeedOptions?.From is ChangeFeedStartFromContinuation startFromContinuation) + if (this.changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation) { this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync( this.containerRid, @@ -108,11 +108,11 @@ internal StandByFeedIteratorCore( if (token.Token != null) { - this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromContinuation(token.Token); + this.changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(token.Token); } else { - this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromBeginning(); + this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning(); } } else @@ -128,11 +128,11 @@ internal StandByFeedIteratorCore( FeedRange feedRange = new FeedRangePartitionKeyRange(rangeId); if (currentRangeToken.Token != null) { - this.changeFeedOptions.From = new ChangeFeedStartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange); } else { - this.changeFeedOptions.From = ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange); + this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning(feedRange); } ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken); @@ -186,7 +186,11 @@ internal virtual Task NextResultSetDelegateAsync( operationType: Documents.OperationType.ReadFeed, requestOptions: options, containerInternal: this.container, - requestEnricher: default, + requestEnricher: (request) => + { + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + this.changeFeedStartFrom.Accept(visitor); + }, responseCreator: response => response, partitionKey: default, streamPayload: default, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index 7174b84476..bcaccbe7b1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -156,10 +156,10 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() IEnumerable pkRangeIds = await container.GetPartitionKeyRangesAsync(feedRange); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - MaxItemCount = 1 + PageSizeHint = 1 }; ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator( - changeFeedStartFrom: ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange), + changeFeedStartFrom: ChangeFeedStartFrom.Beginning(feedRange), changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) @@ -196,10 +196,10 @@ public async Task ChangeFeed_FeedRange_FromV2SDK() { ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - MaxItemCount = 100 + PageSizeHint = 100 }; ChangeFeedIteratorCore feedIterator = container.GetChangeFeedStreamIterator( - changeFeedStartFrom: ChangeFeedStartFrom.CreateFromContinuation(continuation), + changeFeedStartFrom: ChangeFeedStartFrom.ContinuationToken(continuation), changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; ResponseMessage firstResponse = await feedIterator.ReadNextAsync(); if (firstResponse.IsSuccessStatusCode) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs index eb93f8c81b..4c3c88ef67 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -473,7 +473,7 @@ public async Task ChangeFeedDiagnostics(bool disableDiagnostics) ChangeFeedRequestOptions requestOptions = disableDiagnostics ? ChangeFeedRequestOptionDisableDiagnostic : null; FeedIterator changeFeedIterator = ((ContainerCore)(container as ContainerInlineCore)).GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), changeFeedRequestOptions: requestOptions); while (changeFeedIterator.HasMoreResults) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs index d6d333107e..dc6f9256de 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs @@ -66,10 +66,7 @@ public async Task StandByFeedIterator() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerCore itemsCore = (ContainerCore)this.Container; FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( - requestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromBeginning(), - }); + ChangeFeedStartFrom.Beginning()); while (feedIterator.HasMoreResults) { @@ -98,10 +95,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); FeedIterator setIteratorNew = itemsCore.GetStandByFeedIterator( - new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation(lastcontinuation), - }); + ChangeFeedStartFrom.ContinuationToken(lastcontinuation)); while (setIteratorNew.HasMoreResults) { @@ -136,7 +130,8 @@ public async Task StandByFeedIterator_EmptyBeginning() bool createdDocuments = false; ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator(); + FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + ChangeFeedStartFrom.Beginning()); while (feedIterator.HasMoreResults) { @@ -190,10 +185,7 @@ public async Task StandByFeedIterator_WithInexistentRange() ContainerInternal itemsCore = this.Container; FeedIterator setIteratorNew = itemsCore.GetStandByFeedIterator( - requestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation(corruptedTokenSerialized), - }); + ChangeFeedStartFrom.ContinuationToken(corruptedTokenSerialized)); _ = await setIteratorNew.ReadNextAsync(this.cancellationToken); Assert.Fail("Should have thrown."); @@ -208,10 +200,10 @@ public async Task StandByFeedIterator_WithMaxItemCount() await this.CreateRandomItems(this.Container, 2, randomPartitionKey: true); ContainerCore itemsCore = (ContainerCore)this.Container; FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( + ChangeFeedStartFrom.Beginning(), requestOptions: new ChangeFeedRequestOptions() { - MaxItemCount = 1, - From = ChangeFeedStartFrom.CreateFromBeginning(), + PageSizeHint = 1 }); while (feedIterator.HasMoreResults) @@ -248,13 +240,8 @@ public async Task StandByFeedIterator_NoFetchNext() int count = 0; while (true) { - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromBeginning(), - }; - FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( - requestOptions: requestOptions); + ChangeFeedStartFrom.Beginning()); using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { @@ -297,10 +284,7 @@ public async Task StandByFeedIterator_BreathFirst() await this.CreateRandomItems(this.LargerContainer, expected, randomPartitionKey: true); ContainerCore itemsCore = (ContainerCore)this.LargerContainer; FeedIterator feedIterator = itemsCore.GetStandByFeedIterator( - requestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromBeginning() - }); + ChangeFeedStartFrom.Beginning()); while (feedIterator.HasMoreResults) { using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) @@ -328,10 +312,10 @@ public async Task StandByFeedIterator_VerifyRefreshIsCalledOnSplit() { CosmosChangeFeedResultSetIteratorCoreMock iterator = new CosmosChangeFeedResultSetIteratorCoreMock( (ContainerCore)this.Container, - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 100, + PageSizeHint = 100, }); using (ResponseMessage responseMessage = await iterator.ReadNextAsync(this.cancellationToken)) @@ -366,10 +350,7 @@ public async Task GetChangeFeedTokensAsync_AllowsParallelProcessing() int count = 0; FeedIterator iteratorForToken = itemsCore.GetStandByFeedIterator( - requestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation(token), - }); + ChangeFeedStartFrom.ContinuationToken(token)); while (true) { using (ResponseMessage responseMessage = @@ -415,10 +396,7 @@ public async Task GetChangeFeedTokensAsync_DrainFromJustOnePartition() int count = 0; FeedIterator iteratorForToken = itemsCore.GetStandByFeedIterator( - requestOptions: new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation(token), - }); + ChangeFeedStartFrom.ContinuationToken(token)); while (iteratorForToken.HasMoreResults) { using (ResponseMessage responseMessage = await iteratorForToken.ReadNextAsync(this.cancellationToken)) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index 8bb5f32897..cb762ae666 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -62,7 +62,7 @@ public async Task ChangeFeedIteratorCore_ReadAll() await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning()) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -86,7 +86,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.LargerContainer, batchSize, randomPartitionKey: true); ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromContinuation(continuation)) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.ContinuationToken(continuation)) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -119,7 +119,7 @@ public async Task ChangeFeedIteratorCore_StartTime() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; FeedIterator feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromTime(now)); + ChangeFeedStartFrom.Time(now)); while (feedIterator.HasMoreResults) { using (ResponseMessage responseMessage = @@ -162,7 +162,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll() ContainerInternal itemsCore = this.Container; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead)))) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning(new FeedRangePartitionKey(new PartitionKey(pkToRead)))) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { @@ -194,7 +194,7 @@ await feedIterator.ReadNextAsync(this.cancellationToken)) } ChangeFeedIteratorCore setIteratorNew = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromContinuation(continuation)) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.ContinuationToken(continuation)) as ChangeFeedIteratorCore; while (setIteratorNew.HasMoreResults) { @@ -242,7 +242,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() ContainerInternal itemsCore = this.Container; FeedIterator feedIterator = itemsCore.GetChangeFeedIterator( - ChangeFeedStartFrom.CreateFromBeginningWithRange(new FeedRangePartitionKey(new PartitionKey(pkToRead)))); + ChangeFeedStartFrom.Beginning(new FeedRangePartitionKey(new PartitionKey(pkToRead)))); string continuation = null; while (feedIterator.HasMoreResults) { @@ -267,7 +267,7 @@ public async Task ChangeFeedIteratorCore_PartitionKey_OfT_ReadAll() } FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator( - ChangeFeedStartFrom.CreateFromContinuation(continuation)); + ChangeFeedStartFrom.ContinuationToken(continuation)); while (setIteratorNew.HasMoreResults) { @@ -295,7 +295,7 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; - FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.CreateFromBeginning()); + FeedIterator feedIterator = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.Beginning()); string continuation = null; while (feedIterator.HasMoreResults) { @@ -310,7 +310,7 @@ public async Task ChangeFeedIteratorCore_OfT_ReadAll() // Insert another batch of 25 and use the last FeedToken from the first cycle await this.CreateRandomItems(this.Container, batchSize, randomPartitionKey: true); - FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.CreateFromContinuation(continuation)); + FeedIterator setIteratorNew = itemsCore.GetChangeFeedIterator(ChangeFeedStartFrom.ContinuationToken(continuation)); while (setIteratorNew.HasMoreResults) { @@ -334,7 +334,7 @@ public async Task ChangeFeedIteratorCore_EmptyBeginning() ContainerInternal itemsCore = this.Container; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning()) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults || (createdDocuments && totalCount == 0)) @@ -371,10 +371,10 @@ public async Task ChangeFeedIteratorCore_WithMaxItemCount() await this.CreateRandomItems(this.Container, 2, randomPartitionKey: true); ContainerInternal itemsCore = this.Container; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), changeFeedRequestOptions: new ChangeFeedRequestOptions() { - MaxItemCount = 1, + PageSizeHint = 1, }) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) @@ -415,25 +415,17 @@ public async Task ChangeFeedIteratorCore_NoFetchNext() int count = 0; while (true) { - ChangeFeedRequestOptions requestOptions; + ChangeFeedStartFrom startFrom; if (continuation == null) { - requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromBeginning(), - }; + startFrom = ChangeFeedStartFrom.Beginning(); } else { - requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation(continuation), - }; + startFrom = ChangeFeedStartFrom.ContinuationToken(continuation); } - ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning(), - changeFeedRequestOptions: requestOptions) as ChangeFeedIteratorCore; + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator(startFrom) as ChangeFeedIteratorCore; using (ResponseMessage responseMessage = await feedIterator.ReadNextAsync(this.cancellationToken)) { if (responseMessage.IsSuccessStatusCode) @@ -478,10 +470,10 @@ public async Task ChangeFeedIteratorCore_BreathFirst() await this.CreateRandomItems(this.LargerContainer, expected, randomPartitionKey: true); ContainerInternal itemsCore = this.LargerContainer; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 1, + PageSizeHint = 1, }) as ChangeFeedIteratorCore; while (true) { @@ -528,7 +520,7 @@ public async Task GetFeedRangesAsync_AllowsParallelProcessing() int count = 0; ChangeFeedIteratorCore iteratorForToken = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginningWithRange(token)) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning(token)) as ChangeFeedIteratorCore; while (true) { using (ResponseMessage responseMessage = @@ -564,10 +556,10 @@ public async Task CannotMixTokensFromOtherContainers() { IReadOnlyList tokens = await this.LargerContainer.GetFeedRangesAsync(); FeedIterator iterator = this.LargerContainer.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginningWithRange(tokens[0])); + ChangeFeedStartFrom.Beginning(tokens[0])); ResponseMessage responseMessage = await iterator.ReadNextAsync(); iterator = this.Container.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromContinuation(responseMessage.ContinuationToken)); + ChangeFeedStartFrom.ContinuationToken(responseMessage.ContinuationToken)); responseMessage = await iterator.ReadNextAsync(); Assert.IsNotNull(responseMessage.CosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs index 465d9f3291..23f7ed385f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs @@ -120,7 +120,7 @@ public async Task ChangeFeedBaselineAsync() { ChangeFeedIteratorCore feedIterator = ((ContainerCore)this.container) .GetChangeFeedStreamIterator( - ChangeFeedStartFrom.CreateFromBeginning()) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning()) as ChangeFeedIteratorCore; while (feedIterator.HasMoreResults) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 03a69badc4..dac5521dfd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -66,10 +66,10 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 10, + PageSizeHint = 10, }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -128,10 +128,10 @@ public async Task ShouldContinueUntilResponseOk() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 10, + PageSizeHint = 10, }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -193,10 +193,10 @@ public async Task ShouldReturnNotModifiedAfterCyclingOnAllRanges() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 10, + PageSizeHint = 10, }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); @@ -253,10 +253,10 @@ public async Task ShouldReturnNotModifiedOnSingleRange() StandByFeedIteratorCore iterator = new StandByFeedIteratorCore( mockContext.Object, new ContainerInlineCore(mockContext.Object, databaseCore, "myColl"), - ChangeFeedStartFrom.CreateFromBeginning(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = 10, + PageSizeHint = 10, }); ResponseMessage firstRequest = await iterator.ReadNextAsync(); Assert.IsTrue(firstRequest.Headers.ContinuationToken.Contains(firstResponse.Headers.ETag), "Response should contain the first continuation"); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index f8eed1deeb..d3b27b8bb8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -21,7 +21,10 @@ public class ChangeFeedIteratorCoreTests [ExpectedException(typeof(ArgumentNullException))] public void ChangeFeedIteratorCore_Null_Container() { - new ChangeFeedIteratorCore(container: null, new ChangeFeedRequestOptions()); + new ChangeFeedIteratorCore( + container: null, + ChangeFeedStartFrom.Beginning(), + new ChangeFeedRequestOptions()); } [DataTestMethod] @@ -32,16 +35,20 @@ public void ChangeFeedIteratorCore_ValidateOptions(int maxItemCount) { new ChangeFeedIteratorCore( Mock.Of(), + ChangeFeedStartFrom.Beginning(), new ChangeFeedRequestOptions() { - MaxItemCount = maxItemCount + PageSizeHint = maxItemCount }); } [TestMethod] public void ChangeFeedIteratorCore_HasMoreResultsDefault() { - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(Mock.Of(), null); + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + Mock.Of(), + ChangeFeedStartFrom.Beginning(), + null); Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); } @@ -434,9 +441,14 @@ private static CosmosClientContext GetMockedClientContext( return clientContext; } - private static ChangeFeedIteratorCore CreateWithCustomFeedToken(ContainerInternal containerInternal, FeedRangeContinuation feedToken) + private static ChangeFeedIteratorCore CreateWithCustomFeedToken( + ContainerInternal containerInternal, + FeedRangeContinuation feedToken) { - ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore(containerInternal, changeFeedRequestOptions: default); + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + containerInternal, + ChangeFeedStartFrom.Beginning(), + changeFeedRequestOptions: default); System.Reflection.FieldInfo prop = changeFeedIteratorCore .GetType() .GetField( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index 339fcabd31..e4ded559d3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -224,11 +224,8 @@ public void ConstructorWithRangeGeneratesSingleQueue() public void ChangeFeedRequestOptions_ContinuationIsSet() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromContinuation("something"), - }; - requestOptions.PopulateRequestOptions(request); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFrom.ContinuationToken("something").Accept(visitor); Assert.AreEqual(expected: "something", actual: request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); @@ -238,12 +235,8 @@ public void ChangeFeedRequestOptions_ContinuationIsSet() public void ChangeFeedRequestOptions_StartFromNow() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromNow(), - }; - - requestOptions.PopulateRequestOptions(request); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFrom.Now().Accept(visitor); Assert.AreEqual(expected: "*", request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); @@ -253,12 +246,8 @@ public void ChangeFeedRequestOptions_StartFromNow() public void ChangeFeedRequestOptions_StartFromBeginning() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromBeginning(), - }; - - requestOptions.PopulateRequestOptions(request); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFrom.Beginning().Accept(visitor); Assert.IsNull(request.Headers.IfNoneMatch); Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); @@ -282,12 +271,13 @@ public void ChangeFeedRequestOptions_Default() public void ChangeFeedRequestOptions_MaxItemSizeIsSet() { RequestMessage request = new RequestMessage(); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { - MaxItemCount = 10, - From = ChangeFeedStartFrom.CreateFromBeginning(), + PageSizeHint = 10, }; requestOptions.PopulateRequestOptions(request); + ChangeFeedStartFrom.Beginning().Accept(visitor); Assert.AreEqual(expected: "10", actual: request.Headers[Documents.HttpConstants.HttpHeaders.PageSize]); Assert.IsNull(request.Headers.IfNoneMatch); @@ -298,11 +288,8 @@ public void ChangeFeedRequestOptions_MaxItemSizeIsSet() public void ChangeFeedRequestOptions_AddsStartTime() { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = ChangeFeedStartFrom.CreateFromTime(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)), - }; - requestOptions.PopulateRequestOptions(request); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFrom.Time(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Accept(visitor); Assert.AreEqual( expected: "Tue, 01 Jan 1985 00:00:00 GMT", @@ -316,19 +303,16 @@ public void ChangeFeedRequestOptions_AddsFeedRange() FeedRange feedRange = new FeedRangePartitionKeyRange("randomPK"); ChangeFeedStartFrom[] froms = new ChangeFeedStartFrom[] { - ChangeFeedStartFrom.CreateFromBeginningWithRange(feedRange), - ChangeFeedStartFrom.CreateFromNowWithRange(feedRange), - ChangeFeedStartFrom.CreateFromTimeWithRange(DateTime.MinValue.ToUniversalTime(), feedRange) + ChangeFeedStartFrom.Beginning(feedRange), + ChangeFeedStartFrom.Now(feedRange), + ChangeFeedStartFrom.Time(DateTime.MinValue.ToUniversalTime(), feedRange) }; foreach (ChangeFeedStartFrom from in froms) { RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - From = from, - }; - requestOptions.PopulateRequestOptions(request); + PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + from.Accept(visitor); Assert.AreEqual( expected: "randomPK", From 9018e52d78e64ad20fb125a906303b80894dbbf8 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Fri, 7 Aug 2020 11:25:26 -0700 Subject: [PATCH 08/19] resolved iteration comments --- .../src/ChangeFeedStartFrom.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs index dd0297144a..2c8a4a9302 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs @@ -22,7 +22,7 @@ abstract class ChangeFeedStartFrom /// /// Initializes an instance of the class. /// - internal protected ChangeFeedStartFrom() + internal ChangeFeedStartFrom() { // Internal so people can't derive from this type. } @@ -55,32 +55,32 @@ public static ChangeFeedStartFrom Now(FeedRange feedRange) /// /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. /// - /// The time to start reading from. + /// The time (in UTC) to start reading from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom Time(DateTime dateTime) => Time(dateTime, FeedRangeEpk.FullRange); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc) => Time(dateTimeUtc, FeedRangeEpk.FullRange); /// /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. /// - /// The time to start reading from. + /// The time to start reading from. /// The range to start from. /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom Time(DateTime dateTime, FeedRange feedRange) + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange) { if (!(feedRange is FeedRangeInternal feedRangeInternal)) { throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); } - return new ChangeFeedStartFromTime(dateTime, feedRangeInternal); + return new ChangeFeedStartFromTime(dateTimeUtc, feedRangeInternal); } /// /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. /// - /// The continuation to resume from. + /// The continuation to resume from. /// A that tells the ChangeFeed operation to start reading changes from a save point. - public static ChangeFeedStartFrom ContinuationToken(string continuation) => new ChangeFeedStartFromContinuation(continuation); + public static ChangeFeedStartFrom ContinuationToken(string continuationToken) => new ChangeFeedStartFromContinuation(continuationToken); /// /// Creates a that tells the ChangeFeed operation to start from the beginning of time. From beb565001c502ef821b6d06f15e85ec1e0cd1a38 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Fri, 7 Aug 2020 12:19:40 -0700 Subject: [PATCH 09/19] fixed tests --- Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs | 7 +++++-- .../src/Handler/RequestInvokerHandler.cs | 6 +++--- .../QueryResponses/ChangeFeedIteratorCore.cs | 14 ++------------ ...ChangeFeedPartitionKeyResultSetIteratorCore.cs | 15 ++++++--------- .../StandByFeedContinuationTokenTests.cs | 14 -------------- 5 files changed, 16 insertions(+), 40 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs index 2c8a4a9302..d9ed0f73b2 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs @@ -180,7 +180,10 @@ public override void Visit(ChangeFeedStartFromBeginning startFromBeginning) public override void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) { // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; + if (startFromContinuationAndFeedRange.Etag != null) + { + this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; + } startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); } @@ -331,7 +334,7 @@ internal sealed class ChangeFeedStartFromContinuationAndFeedRange : ChangeFeedSt { public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) { - this.Etag = etag ?? throw new ArgumentNullException(nameof(etag)); + this.Etag = etag; this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); } diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs index 9fcf5cdf7a..fe8c625db9 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs @@ -125,9 +125,9 @@ public virtual async Task SendAsync( // user request might span multiple backend requests. // This will still have a single request id for retry scenarios ActivityScope activityScope = ActivityScope.CreateIfDefaultActivityId(); - Debug.Assert(activityScope != null && - (operationType != OperationType.SqlQuery || operationType != OperationType.Query || operationType != OperationType.QueryPlan), - "There should be an activity id already set"); + //Debug.Assert(activityScope != null && + // (operationType != OperationType.SqlQuery || operationType != OperationType.Query || operationType != OperationType.QueryPlan), + // "There should be an activity id already set"); try { diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index f8c7bd9832..b4efbfc532 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -129,18 +129,8 @@ private async Task ReadNextInternalAsync( cancellationToken.ThrowIfCancellationRequested(); string etag = this.FeedRangeContinuation.GetContinuation(); - if (etag != null) - { - FeedRange feedRange = this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); - if ((feedRange == null) || feedRange is FeedRangeEpk) - { - // For now the backend does not support EPK Ranges if they don't line up with a PKRangeId - // So if the range the user supplied is a logical pk value, then we don't want to overwrite it. - feedRange = this.FeedRangeContinuation.GetFeedRange(); - } - - this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); - } + FeedRange feedRange = this.FeedRangeContinuation.GetFeedRange(); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( resourceUri: this.container.LinkUri, diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs index f8f294a72b..58d0924f95 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -20,7 +20,6 @@ internal sealed class ChangeFeedPartitionKeyResultSetIteratorCore : FeedIterator private readonly ChangeFeedRequestOptions changeFeedOptions; private ChangeFeedStartFrom changeFeedStartFrom; private bool hasMoreResultsInternal; - private string continuationToken; public ChangeFeedPartitionKeyResultSetIteratorCore( CosmosClientContext clientContext, @@ -45,12 +44,6 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( /// A change feed response from cosmos service public override async Task ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken)) { - // Change Feed read uses Etag for continuation - if (this.continuationToken != null) - { - this.changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(this.continuationToken); - } - ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( cosmosContainerCore: this.container, resourceUri: this.container.LinkUri, @@ -67,9 +60,13 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( diagnosticsContext: default, cancellationToken: cancellationToken); - this.continuationToken = responseMessage.Headers.ETag; + // Change Feed uses etag as continuation token. + string etag = responseMessage.Headers.ETag; this.hasMoreResultsInternal = responseMessage.IsSuccessStatusCode; - responseMessage.Headers.ContinuationToken = this.continuationToken; + responseMessage.Headers.ContinuationToken = etag; + + FeedRangeInternal feedRange = (FeedRangeInternal)this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, feedRange); return responseMessage; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index e4ded559d3..d745dd1296 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -253,20 +253,6 @@ public void ChangeFeedRequestOptions_StartFromBeginning() Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); } - [TestMethod] - public void ChangeFeedRequestOptions_Default() - { - RequestMessage request = new RequestMessage(); - ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() - { - }; - - requestOptions.PopulateRequestOptions(request); - - Assert.AreEqual(expected: "*", request.Headers.IfNoneMatch); - Assert.IsNull(request.Headers[Documents.HttpConstants.HttpHeaders.IfModifiedSince]); - } - [TestMethod] public void ChangeFeedRequestOptions_MaxItemSizeIsSet() { From 366d0d34d9f1cef62167de24c0cef046f01a55cd Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Fri, 7 Aug 2020 13:00:45 -0700 Subject: [PATCH 10/19] fixed feed range docs --- Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs index 2e58afc279..7af65e32ac 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs @@ -39,6 +39,11 @@ public static FeedRange FromJsonString(string toStringValue) return parsedRange; } + /// + /// Creates a feed range that span only a single value. + /// + /// The partition key value to create a feed range from. + /// The feed range that spans the partition. public static FeedRange CreateFromPartitionKey(PartitionKey partitionKey) => new FeedRangePartitionKey(partitionKey); } } From ab91449f61d56e575726257c43a28deb62631715 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Fri, 7 Aug 2020 13:40:06 -0700 Subject: [PATCH 11/19] removed unreachable code --- .../Query/OfflineEngineTests/PathToken.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/OfflineEngineTests/PathToken.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/OfflineEngineTests/PathToken.cs index 8885f00ba1..442520ea6c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/OfflineEngineTests/PathToken.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/OfflineEngineTests/PathToken.cs @@ -26,8 +26,6 @@ public int CompareTo(PathToken other) throw new InvalidEnumArgumentException($"{nameof(other)}"); } - break; - case StringPathToken stringPathToken1: switch (other) { @@ -41,8 +39,6 @@ public int CompareTo(PathToken other) throw new InvalidEnumArgumentException($"{nameof(other)}"); } - break; - default: throw new InvalidEnumArgumentException($"this"); } @@ -75,8 +71,6 @@ public bool Equals(PathToken other) throw new InvalidEnumArgumentException($"{nameof(other)}"); } - break; - case StringPathToken stringPathToken1: switch (other) { @@ -90,8 +84,6 @@ public bool Equals(PathToken other) throw new InvalidEnumArgumentException($"{nameof(other)}"); } - break; - default: throw new InvalidEnumArgumentException($"this"); } From dd7667935c7fe640c1aed58ba974351978e4e6c3 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Sat, 8 Aug 2020 14:20:58 -0700 Subject: [PATCH 12/19] fixed tests --- .../QueryResponses/ChangeFeedIteratorCore.cs | 4 +- .../Contracts/CosmosVirtualUnitTest.cs | 26 ++- .../Contracts/DotNetPreviewSDKAPI.json | 220 ++++-------------- 3 files changed, 65 insertions(+), 185 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index b4efbfc532..43196b6316 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -129,8 +129,8 @@ private async Task ReadNextInternalAsync( cancellationToken.ThrowIfCancellationRequested(); string etag = this.FeedRangeContinuation.GetContinuation(); - FeedRange feedRange = this.FeedRangeContinuation.GetFeedRange(); - this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + FeedRangeInternal feedRange = this.FeedRangeContinuation.FeedRange; + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, feedRange); ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( resourceUri: this.container.LinkUri, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/CosmosVirtualUnitTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/CosmosVirtualUnitTest.cs index ae8dcc6666..a3744e63f6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/CosmosVirtualUnitTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/CosmosVirtualUnitTest.cs @@ -51,33 +51,43 @@ public void VerifyAllPublicMembersAreVirtualUnitTesting() } [TestMethod] - public void VerifyAllPublicClassesCanBeMockedUnitTesting() + public void VerifyAllPublicClassesCanBeMocked() { + // The following classes are public, but not meant to be mocked. + HashSet nonMockableClasses = new HashSet() + { + "ChangeFeedStartFrom" + }; + // All of the public classes should not contain an internal abstract method // create unit tests by mocking the different types. Data Contracts do not support mocking so exclude all types that end with Settings. IEnumerable allClasses = from t in Assembly.GetAssembly(typeof(CosmosClient)).GetTypes() - where t.IsClass && t.Namespace == "Microsoft.Azure.Cosmos" && t.IsPublic + where + t.IsClass && + t.Namespace == "Microsoft.Azure.Cosmos" && + t.IsPublic && + !nonMockableClasses.Contains(t.Name) select t; // Get the entire list to prevent running the test for each method/property List> nonVirtualPublic = new List>(); - foreach (Type publiClass in allClasses) + foreach (Type publicClass in allClasses) { // DeclaredOnly flag gets only the properties declared in the current class. // This ignores inherited properties to prevent duplicate findings. - IEnumerable> allProps = publiClass.GetProperties(BindingFlags.DeclaredOnly) + IEnumerable> allProps = publicClass.GetProperties(BindingFlags.DeclaredOnly) .Where(x => x.GetGetMethod().IsAbstract && !x.GetGetMethod().IsPublic) - .Select(x => new Tuple(publiClass.FullName, x.Name)); + .Select(x => new Tuple(publicClass.FullName, x.Name)); nonVirtualPublic.AddRange(allProps); - IEnumerable> allMethods = publiClass.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + IEnumerable> allMethods = publicClass.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(x => x.IsAbstract && !x.IsPublic) - .Select(x => new Tuple(publiClass.FullName, x.Name)); + .Select(x => new Tuple(publicClass.FullName, x.Name)); nonVirtualPublic.AddRange(allMethods); } Assert.AreEqual(0, nonVirtualPublic.Count, - "The following methods and properties should be virtual to allow unit testing:" + + "The following methods and properties should be virtual to allow mocking:" + string.Join(";", nonVirtualPublic.Select(x => $"Class:{x.Item1}; Member:{x.Item2}"))); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 06bd33c8b5..ca92934d34 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -3,41 +3,12 @@ "ChangeFeedRequestOptions": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.ChangeFeedRequestOptions+StartFrom": { - "Type": "NestedType", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.FeedRange FeedRange": { - "Type": "Property", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()" - }, - "StartFrom From": { - "Type": "Property", - "Attributes": [], - "MethodInfo": null - }, - "StartFrom get_From()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "StartFrom get_From()" - }, - "System.Nullable`1[System.Int32] get_MaxItemCount()": { + "System.Nullable`1[System.Int32] get_PageSizeHint()": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Nullable`1[System.Int32] get_MaxItemCount()" + "MethodInfo": "System.Nullable`1[System.Int32] get_PageSizeHint()" }, - "System.Nullable`1[System.Int32] MaxItemCount": { + "System.Nullable`1[System.Int32] PageSizeHint": { "Type": "Property", "Attributes": [], "MethodInfo": null @@ -73,20 +44,6 @@ "Attributes": [], "MethodInfo": "Void .ctor()" }, - "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)" - }, - "Void set_From(StartFrom)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_From(StartFrom)" - }, "Void set_IfMatchEtag(System.String)": { "Type": "Method", "Attributes": [], @@ -97,63 +54,51 @@ "Attributes": [], "MethodInfo": "Void set_IfNoneMatchEtag(System.String)" }, - "Void set_MaxItemCount(System.Nullable`1[System.Int32])": { + "Void set_PageSizeHint(System.Nullable`1[System.Int32])": { "Type": "Method", "Attributes": [], - "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" + "MethodInfo": "Void set_PageSizeHint(System.Nullable`1[System.Int32])" } }, - "NestedTypes": { - "StartFrom": { - "Subclasses": {}, - "Members": { - "StartFrom CreateFromBeginning()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromBeginning()" - }, - "StartFrom CreateFromContinuation(System.String)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromContinuation(System.String)" - }, - "StartFrom CreateFromNow()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromNow()" - }, - "StartFrom CreateFromTime(System.DateTime)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" - } - }, - "NestedTypes": {} - } - } + "NestedTypes": {} }, - "StartFrom": { + "ChangeFeedStartFrom": { "Subclasses": {}, "Members": { - "StartFrom CreateFromBeginning()": { + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Beginning()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Beginning()" + }, + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Beginning(Microsoft.Azure.Cosmos.FeedRange)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Beginning(Microsoft.Azure.Cosmos.FeedRange)" + }, + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom ContinuationToken(System.String)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom ContinuationToken(System.String)" + }, + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Now()": { "Type": "Method", "Attributes": [], - "MethodInfo": "StartFrom CreateFromBeginning()" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Now()" }, - "StartFrom CreateFromContinuation(System.String)": { + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Now(Microsoft.Azure.Cosmos.FeedRange)": { "Type": "Method", "Attributes": [], - "MethodInfo": "StartFrom CreateFromContinuation(System.String)" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Now(Microsoft.Azure.Cosmos.FeedRange)" }, - "StartFrom CreateFromNow()": { + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Time(System.DateTime)": { "Type": "Method", "Attributes": [], - "MethodInfo": "StartFrom CreateFromNow()" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Time(System.DateTime)" }, - "StartFrom CreateFromTime(System.DateTime)": { + "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Time(System.DateTime, Microsoft.Azure.Cosmos.FeedRange)": { "Type": "Method", "Attributes": [], - "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedStartFrom Time(System.DateTime, Microsoft.Azure.Cosmos.FeedRange)" } }, "NestedTypes": {} @@ -161,30 +106,20 @@ "Container": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" - }, - "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { + "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedStartFrom, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetChangeFeedStreamIterator(Microsoft.Azure.Cosmos.ChangeFeedStartFrom, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, "Microsoft.Azure.Cosmos.FeedIterator GetItemQueryStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)": { "Type": "Method", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator GetItemQueryStreamIterator(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)" }, - "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" - }, - "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { + "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedStartFrom, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetChangeFeedIterator[T](Microsoft.Azure.Cosmos.ChangeFeedStartFrom, Microsoft.Azure.Cosmos.ChangeFeedRequestOptions)" }, "Microsoft.Azure.Cosmos.FeedIterator`1[T] GetItemQueryIterator[T](Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.QueryDefinition, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions)": { "Type": "Method", @@ -244,6 +179,11 @@ "FeedRange": { "Subclasses": {}, "Members": { + "Microsoft.Azure.Cosmos.FeedRange CreateFromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange CreateFromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)" + }, "Microsoft.Azure.Cosmos.FeedRange FromJsonString(System.String)": { "Type": "Method", "Attributes": [], @@ -275,41 +215,12 @@ "ChangeFeedRequestOptions": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.ChangeFeedRequestOptions+StartFrom": { - "Type": "NestedType", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.FeedRange FeedRange": { - "Type": "Property", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()" - }, - "StartFrom From": { - "Type": "Property", - "Attributes": [], - "MethodInfo": null - }, - "StartFrom get_From()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "StartFrom get_From()" - }, - "System.Nullable`1[System.Int32] get_MaxItemCount()": { + "System.Nullable`1[System.Int32] get_PageSizeHint()": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Nullable`1[System.Int32] get_MaxItemCount()" + "MethodInfo": "System.Nullable`1[System.Int32] get_PageSizeHint()" }, - "System.Nullable`1[System.Int32] MaxItemCount": { + "System.Nullable`1[System.Int32] PageSizeHint": { "Type": "Property", "Attributes": [], "MethodInfo": null @@ -345,20 +256,6 @@ "Attributes": [], "MethodInfo": "Void .ctor()" }, - "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_FeedRange(Microsoft.Azure.Cosmos.FeedRange)" - }, - "Void set_From(StartFrom)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_From(StartFrom)" - }, "Void set_IfMatchEtag(System.String)": { "Type": "Method", "Attributes": [], @@ -369,40 +266,13 @@ "Attributes": [], "MethodInfo": "Void set_IfNoneMatchEtag(System.String)" }, - "Void set_MaxItemCount(System.Nullable`1[System.Int32])": { + "Void set_PageSizeHint(System.Nullable`1[System.Int32])": { "Type": "Method", "Attributes": [], - "MethodInfo": "Void set_MaxItemCount(System.Nullable`1[System.Int32])" + "MethodInfo": "Void set_PageSizeHint(System.Nullable`1[System.Int32])" } }, - "NestedTypes": { - "StartFrom": { - "Subclasses": {}, - "Members": { - "StartFrom CreateFromBeginning()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromBeginning()" - }, - "StartFrom CreateFromContinuation(System.String)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromContinuation(System.String)" - }, - "StartFrom CreateFromNow()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromNow()" - }, - "StartFrom CreateFromTime(System.DateTime)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "StartFrom CreateFromTime(System.DateTime)" - } - }, - "NestedTypes": {} - } - } + "NestedTypes": {} } }, "Members": { From cdfdd1e84eea58afd5a8cab8d16450d77dd69278 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Sun, 9 Aug 2020 17:38:34 -0700 Subject: [PATCH 13/19] fixed tests --- .../QueryResponses/ChangeFeedIteratorCore.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs index 43196b6316..323dae0681 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs @@ -128,10 +128,6 @@ private async Task ReadNextInternalAsync( { cancellationToken.ThrowIfCancellationRequested(); - string etag = this.FeedRangeContinuation.GetContinuation(); - FeedRangeInternal feedRange = this.FeedRangeContinuation.FeedRange; - this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, feedRange); - ResponseMessage responseMessage = await this.clientContext.ProcessResourceOperationStreamAsync( resourceUri: this.container.LinkUri, resourceType: ResourceType.Document, @@ -150,6 +146,10 @@ private async Task ReadNextInternalAsync( if (await this.ShouldRetryAsync(responseMessage, cancellationToken)) { + string etag = this.FeedRangeContinuation.GetContinuation(); + FeedRange feedRange = this.FeedRangeContinuation.GetFeedRange(); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + return await this.ReadNextInternalAsync(diagnosticsScope, cancellationToken); } @@ -159,6 +159,11 @@ private async Task ReadNextInternalAsync( // Change Feed read uses Etag for continuation this.hasMoreResults = responseMessage.IsSuccessStatusCode; this.FeedRangeContinuation.ReplaceContinuation(responseMessage.Headers.ETag); + + string etag = this.FeedRangeContinuation.GetContinuation(); + FeedRange feedRange = this.FeedRangeContinuation.GetFeedRange(); + this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)feedRange); + return FeedRangeResponse.CreateSuccess( responseMessage, this.FeedRangeContinuation); From 223d91da1839651884c00a33c7bd7b1291ec4648 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Sun, 9 Aug 2020 22:19:47 -0700 Subject: [PATCH 14/19] fixed mocks --- .../FeedRange/ChangeFeedIteratorCoreTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index d3b27b8bb8..4808f2d193 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -88,6 +88,9 @@ public async Task ChangeFeedIteratorCore_ReadNextAsync() Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); + Mock.Get(feedToken) + .Setup(f => f.GetFeedRange()) + .Returns(range); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -142,6 +145,9 @@ public async Task ChangeFeedIteratorCore_OfT_ReadNextAsync() Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); + Mock.Get(feedToken) + .Setup(f => f.GetFeedRange()) + .Returns(range); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -206,6 +212,9 @@ public async Task ChangeFeedIteratorCore_UpdatesContinuation_On304() Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); + Mock.Get(feedToken) + .Setup(f => f.GetFeedRange()) + .Returns(range); Mock.Get(feedToken) .Setup(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(Documents.ShouldRetryResult.NoRetry())); @@ -315,6 +324,9 @@ public async Task ChangeFeedIteratorCore_Retries() Mock.Get(feedToken) .Setup(f => f.FeedRange) .Returns(range); + Mock.Get(feedToken) + .Setup(f => f.GetFeedRange()) + .Returns(range); Mock.Get(feedToken) .SetupSequence(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny())) @@ -332,6 +344,10 @@ public async Task ChangeFeedIteratorCore_Retries() Mock.Get(feedToken) .Verify(f => f.ReplaceContinuation(It.IsAny()), Times.Once); + Mock.Get(feedToken) + .Setup(f => f.GetFeedRange()) + .Returns(range); + Mock.Get(feedToken) .Verify(f => f.HandleSplitAsync(It.Is(c => c == containerCore), It.IsAny(), It.IsAny()), Times.Exactly(2)); Mock.Get(feedToken) From 860f2217efa62cba87b0e5a2c10bfa3473e4796b Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Mon, 10 Aug 2020 09:15:18 -0700 Subject: [PATCH 15/19] resolved iteration comments --- .../ChangeFeedIteratorCore.cs | 8 +- ...geFeedPartitionKeyResultSetIteratorCore.cs | 6 +- .../ChangeFeed/ChangeFeedRangeExtractor.cs | 28 ++ .../src/ChangeFeed/ChangeFeedStartFrom.cs | 105 ++++++ .../ChangeFeedStartFromBeginning.cs | 33 ++ .../ChangeFeedStartFromContinuation.cs | 39 ++ ...geFeedStartFromContinuationAndFeedRange.cs | 28 ++ .../src/ChangeFeed/ChangeFeedStartFromNow.cs | 33 ++ ...angeFeedStartFromRequestOptionPopulator.cs | 77 ++++ .../src/ChangeFeed/ChangeFeedStartFromTime.cs | 45 +++ .../ChangeFeed/ChangeFeedStartFromVisitor.cs | 15 + .../ChangeFeedStartFromVisitor{TResult}.cs | 15 + .../StandByFeedContinuationToken.cs | 2 +- .../StandByFeedIteratorCore.cs | 7 +- .../src/ChangeFeedStartFrom.cs | 349 ------------------ .../src/FeedRange/FeedRange.cs | 2 +- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 5 + .../src/Resource/Container/Container.cs | 12 +- .../src/Resource/Container/ContainerCore.cs | 1 + .../FeedToken/ChangeFeedIteratorCoreTests.cs | 5 +- .../FeedRange/ChangeFeedIteratorCoreTests.cs | 1 + 21 files changed, 448 insertions(+), 368 deletions(-) rename Microsoft.Azure.Cosmos/src/{Resource/QueryResponses => ChangeFeed}/ChangeFeedIteratorCore.cs (97%) rename Microsoft.Azure.Cosmos/src/{Resource/QueryResponses => ChangeFeed}/ChangeFeedPartitionKeyResultSetIteratorCore.cs (93%) create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedRangeExtractor.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFrom.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromBeginning.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuation.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuationAndFeedRange.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromNow.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromRequestOptionPopulator.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromTime.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor.cs create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor{TResult}.cs rename Microsoft.Azure.Cosmos/src/{Resource/QueryResponses => ChangeFeed}/StandByFeedContinuationToken.cs (99%) rename Microsoft.Azure.Cosmos/src/{Resource/QueryResponses => ChangeFeed}/StandByFeedIteratorCore.cs (97%) delete mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs similarity index 97% rename from Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs rename to Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs index 323dae0681..3c4d475080 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos +namespace Microsoft.Azure.Cosmos.ChangeFeed { using System; using System.Collections.Generic; @@ -74,7 +74,7 @@ public override async Task ReadNextAsync(CancellationToken canc { diagnostics.AddDiagnosticsInternal( new FeedRangeStatistics( - this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton))); + this.changeFeedStartFrom.Accept(ChangeFeedRangeExtractor.Singleton))); if (!this.lazyContainerRid.ValueInitialized) { using (diagnostics.CreateScope("InitializeContainerResourceId")) @@ -136,7 +136,7 @@ private async Task ReadNextInternalAsync( cosmosContainerCore: this.container, requestEnricher: (request) => { - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); this.changeFeedStartFrom.Accept(visitor); }, partitionKey: default, @@ -214,7 +214,7 @@ private async Task InitializeFeedContinuationAsync(CancellationToken cancellatio { FeedRangePartitionKeyRangeExtractor feedRangePartitionKeyRangeExtractor = new FeedRangePartitionKeyRangeExtractor(this.container); - FeedRange feedRange = this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); + FeedRange feedRange = this.changeFeedStartFrom.Accept(ChangeFeedRangeExtractor.Singleton); IReadOnlyList> ranges = await ((FeedRangeInternal)feedRange).AcceptAsync( feedRangePartitionKeyRangeExtractor, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs similarity index 93% rename from Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs rename to Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 58d0924f95..01a40e292e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos +namespace Microsoft.Azure.Cosmos.ChangeFeed { using System; using System.Net; @@ -52,7 +52,7 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( requestOptions: this.changeFeedOptions, requestEnricher: (requestMessage) => { - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(requestMessage); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(requestMessage); this.changeFeedStartFrom.Accept(visitor); }, partitionKey: default, @@ -65,7 +65,7 @@ public ChangeFeedPartitionKeyResultSetIteratorCore( this.hasMoreResultsInternal = responseMessage.IsSuccessStatusCode; responseMessage.Headers.ContinuationToken = etag; - FeedRangeInternal feedRange = (FeedRangeInternal)this.changeFeedStartFrom.Accept(FeedRangeExtractor.Singleton); + FeedRangeInternal feedRange = (FeedRangeInternal)this.changeFeedStartFrom.Accept(ChangeFeedRangeExtractor.Singleton); this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, feedRange); return responseMessage; diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedRangeExtractor.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedRangeExtractor.cs new file mode 100644 index 0000000000..79d674d245 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedRangeExtractor.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + internal sealed class ChangeFeedRangeExtractor : ChangeFeedStartFromVisitor + { + public static readonly ChangeFeedRangeExtractor Singleton = new ChangeFeedRangeExtractor(); + + private ChangeFeedRangeExtractor() + { + } + + public override FeedRange Visit(ChangeFeedStartFromNow startFromNow) => startFromNow.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromTime startFromTime) => startFromTime.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromContinuation startFromContinuation) + => throw new NotSupportedException($"{nameof(ChangeFeedStartFromContinuation)} does not have a feed range."); + + public override FeedRange Visit(ChangeFeedStartFromBeginning startFromBeginning) => startFromBeginning.FeedRange; + + public override FeedRange Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) => startFromContinuationAndFeedRange.FeedRange; + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFrom.cs new file mode 100644 index 0000000000..8de9024bc5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFrom.cs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using Microsoft.Azure.Cosmos.ChangeFeed; + + /// + /// Base class for where to start a ChangeFeed operation in . + /// + /// Use one of the static constructors to generate a StartFrom option. +#if PREVIEW + public +#else + internal +#endif + abstract class ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + internal ChangeFeedStartFrom() + { + // Internal so people can't derive from this type. + } + + internal abstract void Accept(ChangeFeedStartFromVisitor visitor); + + internal abstract TResult Accept(ChangeFeedStartFromVisitor visitor); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static ChangeFeedStartFrom Now() => Now(FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from this moment onward. + public static ChangeFeedStartFrom Now(FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromNow(feedRangeInternal); + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time (in UTC) to start reading from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc) => Time(dateTimeUtc, FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + /// The time to start reading from. + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromTime(dateTimeUtc, feedRangeInternal); + } + + /// + /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. + /// + /// The continuation to resume from. + /// A that tells the ChangeFeed operation to start reading changes from a save point. + public static ChangeFeedStartFrom ContinuationToken(string continuationToken) => new ChangeFeedStartFromContinuation(continuationToken); + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static ChangeFeedStartFrom Beginning() => Beginning(FeedRangeEpk.FullRange); + + /// + /// Creates a that tells the ChangeFeed operation to start from the beginning of time. + /// + /// The range to start from. + /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. + public static ChangeFeedStartFrom Beginning(FeedRange feedRange) + { + if (!(feedRange is FeedRangeInternal feedRangeInternal)) + { + throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); + } + + return new ChangeFeedStartFromBeginning(feedRangeInternal); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromBeginning.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromBeginning.cs new file mode 100644 index 0000000000..643a122a08 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromBeginning.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from the beginning of time. + /// + internal sealed class ChangeFeedStartFromBeginning : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The (optional) range to start from. + public ChangeFeedStartFromBeginning(FeedRangeInternal feedRange) + : base() + { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuation.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuation.cs new file mode 100644 index 0000000000..feabbad1ba --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuation.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. + /// + /// This class is used to temporarily store the fully serialized composite continuation token and needs to transformed into a . + internal sealed class ChangeFeedStartFromContinuation : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The continuation to resume from. + public ChangeFeedStartFromContinuation(string continuation) + : base() + { + if (string.IsNullOrWhiteSpace(continuation)) + { + throw new ArgumentOutOfRangeException($"{nameof(continuation)} must not be null, empty, or whitespace."); + } + + this.Continuation = continuation; + } + + /// + /// Gets the continuation to resume from. + /// + public string Continuation { get; } + + internal override void Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuationAndFeedRange.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuationAndFeedRange.cs new file mode 100644 index 0000000000..c07bcc270f --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromContinuationAndFeedRange.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading from an LSN for a particular feed range. + /// + internal sealed class ChangeFeedStartFromContinuationAndFeedRange : ChangeFeedStartFrom + { + public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) + { + this.Etag = etag; + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + public string Etag { get; } + + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromNow.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromNow.cs new file mode 100644 index 0000000000..598ac1cae3 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromNow.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. + /// + internal sealed class ChangeFeedStartFromNow : ChangeFeedStartFrom + { + /// + /// Intializes an instance of the class. + /// + /// The (optional) feed range to start from. + public ChangeFeedStartFromNow(FeedRangeInternal feedRange) + : base() + { + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromRequestOptionPopulator.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromRequestOptionPopulator.cs new file mode 100644 index 0000000000..b090666d2d --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromRequestOptionPopulator.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + using System.Globalization; + using Microsoft.Azure.Documents; + + internal sealed class ChangeFeedStartFromRequestOptionPopulator : ChangeFeedStartFromVisitor + { + private const string IfNoneMatchAllHeaderValue = "*"; + private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); + + private readonly RequestMessage requestMessage; + private readonly FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor; + + public ChangeFeedStartFromRequestOptionPopulator(RequestMessage requestMessage) + { + this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); + this.feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); + } + + public override void Visit(ChangeFeedStartFromNow startFromNow) + { + this.requestMessage.Headers.IfNoneMatch = ChangeFeedStartFromRequestOptionPopulator.IfNoneMatchAllHeaderValue; + + if (startFromNow.FeedRange != null) + { + startFromNow.FeedRange.Accept(this.feedRangeVisitor); + } + } + + public override void Visit(ChangeFeedStartFromTime startFromTime) + { + // Our current public contract for ChangeFeedProcessor uses DateTime.MinValue.ToUniversalTime as beginning. + // We need to add a special case here, otherwise it would send it as normal StartTime. + // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, + // it would break multi master Change Feed Processor users using Start From Beginning semantics. + // It's also an optimization, since the backend won't have to binary search for the value. + if (startFromTime.StartTime != ChangeFeedStartFromRequestOptionPopulator.StartFromBeginningTime) + { + this.requestMessage.Headers.Add( + HttpConstants.HttpHeaders.IfModifiedSince, + startFromTime.StartTime.ToString("r", CultureInfo.InvariantCulture)); + } + + startFromTime.FeedRange.Accept(this.feedRangeVisitor); + } + + public override void Visit(ChangeFeedStartFromContinuation startFromContinuation) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + this.requestMessage.Headers.IfNoneMatch = startFromContinuation.Continuation; + } + + public override void Visit(ChangeFeedStartFromBeginning startFromBeginning) + { + // We don't need to set any headers to start from the beginning + + // Except for the feed range. + startFromBeginning.FeedRange.Accept(this.feedRangeVisitor); + } + + public override void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) + { + // On REST level, change feed is using IfNoneMatch/ETag instead of continuation + if (startFromContinuationAndFeedRange.Etag != null) + { + this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; + } + + startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromTime.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromTime.cs new file mode 100644 index 0000000000..26aeb17714 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromTime.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + using System; + + /// + /// Derived instance of that tells the ChangeFeed operation to start reading changes from some point in time onward. + /// + internal sealed class ChangeFeedStartFromTime : ChangeFeedStartFrom + { + /// + /// Initializes an instance of the class. + /// + /// The time to start reading from. + /// The (optional) range to start from. + public ChangeFeedStartFromTime(DateTime time, FeedRangeInternal feedRange) + : base() + { + if (time.Kind != DateTimeKind.Utc) + { + throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); + } + + this.StartTime = time; + this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); + } + + /// + /// Gets the time the ChangeFeed operation should start reading from. + /// + public DateTime StartTime { get; } + + /// + /// Gets the (optional) range to start from. + /// + public FeedRangeInternal FeedRange { get; } + + internal override void Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + + internal override TResult Accept(ChangeFeedStartFromVisitor visitor) => visitor.Visit(this); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor.cs new file mode 100644 index 0000000000..960a6a6745 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + internal abstract class ChangeFeedStartFromVisitor + { + public abstract void Visit(ChangeFeedStartFromNow startFromNow); + public abstract void Visit(ChangeFeedStartFromTime startFromTime); + public abstract void Visit(ChangeFeedStartFromContinuation startFromContinuation); + public abstract void Visit(ChangeFeedStartFromBeginning startFromBeginning); + public abstract void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor{TResult}.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor{TResult}.cs new file mode 100644 index 0000000000..fe52cf5d42 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedStartFromVisitor{TResult}.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + internal abstract class ChangeFeedStartFromVisitor + { + public abstract TResult Visit(ChangeFeedStartFromNow startFromNow); + public abstract TResult Visit(ChangeFeedStartFromTime startFromTime); + public abstract TResult Visit(ChangeFeedStartFromContinuation startFromContinuation); + public abstract TResult Visit(ChangeFeedStartFromBeginning startFromBeginning); + public abstract TResult Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedContinuationToken.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedContinuationToken.cs similarity index 99% rename from Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedContinuationToken.cs rename to Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedContinuationToken.cs index afec119dac..07ddc8cc81 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedContinuationToken.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedContinuationToken.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Query +namespace Microsoft.Azure.Cosmos.ChangeFeed { using System; using System.Collections.Generic; diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedIteratorCore.cs similarity index 97% rename from Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs rename to Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedIteratorCore.cs index db6313ab8a..1c3b8fb969 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/StandByFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/StandByFeedIteratorCore.cs @@ -2,18 +2,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos +namespace Microsoft.Azure.Cosmos.ChangeFeed { using System; using System.Net; - using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.Azure.Cosmos.Routing; - using Microsoft.Azure.Documents.Routing; /// /// Cosmos Stand-By Feed iterator implementing Composite Continuation Token @@ -188,7 +185,7 @@ internal virtual Task NextResultSetDelegateAsync( containerInternal: this.container, requestEnricher: (request) => { - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); this.changeFeedStartFrom.Accept(visitor); }, responseCreator: response => response, diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs deleted file mode 100644 index d9ed0f73b2..0000000000 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedStartFrom.cs +++ /dev/null @@ -1,349 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Globalization; - using Microsoft.Azure.Documents; - - /// - /// Base class for where to start a ChangeFeed operation in . - /// - /// Use one of the static constructors to generate a StartFrom option. -#if PREVIEW - public -#else - internal -#endif - abstract class ChangeFeedStartFrom - { - /// - /// Initializes an instance of the class. - /// - internal ChangeFeedStartFrom() - { - // Internal so people can't derive from this type. - } - - internal abstract void Accept(StartFromVisitor visitor); - - internal abstract TResult Accept(StartFromVisitor visitor); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static ChangeFeedStartFrom Now() => Now(FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from this moment onward. - public static ChangeFeedStartFrom Now(FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new ChangeFeedStartFromNow(feedRangeInternal); - } - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - /// The time (in UTC) to start reading from. - /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom Time(DateTime dateTimeUtc) => Time(dateTimeUtc, FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - /// The time to start reading from. - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from some point in time onward. - public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new ChangeFeedStartFromTime(dateTimeUtc, feedRangeInternal); - } - - /// - /// Creates a that tells the ChangeFeed operation to start reading changes from a save point. - /// - /// The continuation to resume from. - /// A that tells the ChangeFeed operation to start reading changes from a save point. - public static ChangeFeedStartFrom ContinuationToken(string continuationToken) => new ChangeFeedStartFromContinuation(continuationToken); - - /// - /// Creates a that tells the ChangeFeed operation to start from the beginning of time. - /// - /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static ChangeFeedStartFrom Beginning() => Beginning(FeedRangeEpk.FullRange); - - /// - /// Creates a that tells the ChangeFeed operation to start from the beginning of time. - /// - /// The range to start from. - /// A that tells the ChangeFeed operation to start reading changes from the beginning of time. - public static ChangeFeedStartFrom Beginning(FeedRange feedRange) - { - if (!(feedRange is FeedRangeInternal feedRangeInternal)) - { - throw new ArgumentException($"{nameof(feedRange)} needs to be a {nameof(FeedRangeInternal)}."); - } - - return new ChangeFeedStartFromBeginning(feedRangeInternal); - } - } - - internal abstract class StartFromVisitor - { - public abstract void Visit(ChangeFeedStartFromNow startFromNow); - public abstract void Visit(ChangeFeedStartFromTime startFromTime); - public abstract void Visit(ChangeFeedStartFromContinuation startFromContinuation); - public abstract void Visit(ChangeFeedStartFromBeginning startFromBeginning); - public abstract void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); - } - - internal abstract class StartFromVisitor - { - public abstract TResult Visit(ChangeFeedStartFromNow startFromNow); - public abstract TResult Visit(ChangeFeedStartFromTime startFromTime); - public abstract TResult Visit(ChangeFeedStartFromContinuation startFromContinuation); - public abstract TResult Visit(ChangeFeedStartFromBeginning startFromBeginning); - public abstract TResult Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange); - } - - internal sealed class PopulateStartFromRequestOptionVisitor : StartFromVisitor - { - private const string IfNoneMatchAllHeaderValue = "*"; - private static readonly DateTime StartFromBeginningTime = DateTime.MinValue.ToUniversalTime(); - - private readonly RequestMessage requestMessage; - private readonly FeedRangeRequestMessagePopulatorVisitor feedRangeVisitor; - - public PopulateStartFromRequestOptionVisitor(RequestMessage requestMessage) - { - this.requestMessage = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); - this.feedRangeVisitor = new FeedRangeRequestMessagePopulatorVisitor(requestMessage); - } - - public override void Visit(ChangeFeedStartFromNow startFromNow) - { - this.requestMessage.Headers.IfNoneMatch = PopulateStartFromRequestOptionVisitor.IfNoneMatchAllHeaderValue; - - if (startFromNow.FeedRange != null) - { - startFromNow.FeedRange.Accept(this.feedRangeVisitor); - } - } - - public override void Visit(ChangeFeedStartFromTime startFromTime) - { - // Our current public contract for ChangeFeedProcessor uses DateTime.MinValue.ToUniversalTime as beginning. - // We need to add a special case here, otherwise it would send it as normal StartTime. - // The problem is Multi master accounts do not support StartTime header on ReadFeed, and thus, - // it would break multi master Change Feed Processor users using Start From Beginning semantics. - // It's also an optimization, since the backend won't have to binary search for the value. - if (startFromTime.StartTime != PopulateStartFromRequestOptionVisitor.StartFromBeginningTime) - { - this.requestMessage.Headers.Add( - HttpConstants.HttpHeaders.IfModifiedSince, - startFromTime.StartTime.ToString("r", CultureInfo.InvariantCulture)); - } - - startFromTime.FeedRange.Accept(this.feedRangeVisitor); - } - - public override void Visit(ChangeFeedStartFromContinuation startFromContinuation) - { - // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - this.requestMessage.Headers.IfNoneMatch = startFromContinuation.Continuation; - } - - public override void Visit(ChangeFeedStartFromBeginning startFromBeginning) - { - // We don't need to set any headers to start from the beginning - - // Except for the feed range. - startFromBeginning.FeedRange.Accept(this.feedRangeVisitor); - } - - public override void Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) - { - // On REST level, change feed is using IfNoneMatch/ETag instead of continuation - if (startFromContinuationAndFeedRange.Etag != null) - { - this.requestMessage.Headers.IfNoneMatch = startFromContinuationAndFeedRange.Etag; - } - - startFromContinuationAndFeedRange.FeedRange.Accept(this.feedRangeVisitor); - } - } - - internal sealed class FeedRangeExtractor : StartFromVisitor - { - public static readonly FeedRangeExtractor Singleton = new FeedRangeExtractor(); - - private FeedRangeExtractor() - { - } - - public override FeedRange Visit(ChangeFeedStartFromNow startFromNow) => startFromNow.FeedRange; - - public override FeedRange Visit(ChangeFeedStartFromTime startFromTime) => startFromTime.FeedRange; - - public override FeedRange Visit(ChangeFeedStartFromContinuation startFromContinuation) - => throw new NotSupportedException($"{nameof(ChangeFeedStartFromContinuation)} does not have a feed range."); - - public override FeedRange Visit(ChangeFeedStartFromBeginning startFromBeginning) => startFromBeginning.FeedRange; - - public override FeedRange Visit(ChangeFeedStartFromContinuationAndFeedRange startFromContinuationAndFeedRange) => startFromContinuationAndFeedRange.FeedRange; - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from this moment onward. - /// - internal sealed class ChangeFeedStartFromNow : ChangeFeedStartFrom - { - /// - /// Intializes an instance of the class. - /// - /// The (optional) feed range to start from. - public ChangeFeedStartFromNow(FeedRangeInternal feedRange) - : base() - { - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from some point in time onward. - /// - internal sealed class ChangeFeedStartFromTime : ChangeFeedStartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The time to start reading from. - /// The (optional) range to start from. - public ChangeFeedStartFromTime(DateTime time, FeedRangeInternal feedRange) - : base() - { - if (time.Kind != DateTimeKind.Utc) - { - throw new ArgumentOutOfRangeException($"{nameof(time)}.{nameof(DateTime.Kind)} must be {nameof(DateTimeKind)}.{nameof(DateTimeKind.Utc)}"); - } - - this.StartTime = time; - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the time the ChangeFeed operation should start reading from. - /// - public DateTime StartTime { get; } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from a save point. - /// - /// This class is used to temporarily store the fully serialized composite continuation token and needs to transformed into a . - internal sealed class ChangeFeedStartFromContinuation : ChangeFeedStartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The continuation to resume from. - public ChangeFeedStartFromContinuation(string continuation) - : base() - { - if (string.IsNullOrWhiteSpace(continuation)) - { - throw new ArgumentOutOfRangeException($"{nameof(continuation)} must not be null, empty, or whitespace."); - } - - this.Continuation = continuation; - } - - /// - /// Gets the continuation to resume from. - /// - public string Continuation { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading changes from the beginning of time. - /// - internal sealed class ChangeFeedStartFromBeginning : ChangeFeedStartFrom - { - /// - /// Initializes an instance of the class. - /// - /// The (optional) range to start from. - public ChangeFeedStartFromBeginning(FeedRangeInternal feedRange) - : base() - { - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - /// - /// Gets the (optional) range to start from. - /// - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } - - /// - /// Derived instance of that tells the ChangeFeed operation to start reading from an LSN for a particular feed range. - /// - internal sealed class ChangeFeedStartFromContinuationAndFeedRange : ChangeFeedStartFrom - { - public ChangeFeedStartFromContinuationAndFeedRange(string etag, FeedRangeInternal feedRange) - { - this.Etag = etag; - this.FeedRange = feedRange ?? throw new ArgumentNullException(nameof(feedRange)); - } - - public string Etag { get; } - - public FeedRangeInternal FeedRange { get; } - - internal override void Accept(StartFromVisitor visitor) => visitor.Visit(this); - - internal override TResult Accept(StartFromVisitor visitor) => visitor.Visit(this); - } -} diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs index 7af65e32ac..8ef5a5ea86 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRange.cs @@ -44,6 +44,6 @@ public static FeedRange FromJsonString(string toStringValue) /// /// The partition key value to create a feed range from. /// The feed range that spans the partition. - public static FeedRange CreateFromPartitionKey(PartitionKey partitionKey) => new FeedRangePartitionKey(partitionKey); + public static FeedRange FromPartitionKey(PartitionKey partitionKey) => new FeedRangePartitionKey(partitionKey); } } diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index a314effffb..f4756ea65f 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -64,6 +64,11 @@ internal SubStatusCodes SubStatusCode [CosmosKnownHeaderAttribute(HeaderName = HttpConstants.HttpHeaders.Continuation)] public virtual string ContinuationToken { get; internal set; } + internal void Add(object ifModifiedSince, string v) + { + throw new NotImplementedException(); + } + /// /// Gets the request charge for this request from the Azure Cosmos DB service. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 2cdcccaa3a..af35754246 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1188,10 +1188,12 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( /// /// ChangeFeedRequestOptions options = new ChangeFeedRequestOptions() /// { - /// FeedRange = feedRanges[0] + /// PageSizeHint = 10, /// } /// - /// FeedIterator feedIterator = this.Container.GetChangeFeedStreamIterator(options); + /// FeedIterator feedIterator = this.Container.GetChangeFeedStreamIterator( + /// ChangeFeedStartFrom.Beginning(feedRanges[0]), + /// options); /// /// while (feedIterator.HasMoreResults) /// { @@ -1230,10 +1232,12 @@ public abstract FeedIterator GetChangeFeedStreamIterator( /// /// ChangeFeedRequestOptions options = new ChangeFeedRequestOptions() /// { - /// FeedRange = feedRanges[0] + /// PageSizeHint = 10, /// } /// - /// FeedIterator feedIterator = this.Container.GetChangeFeedIterator(options); + /// FeedIterator feedIterator = this.Container.GetChangeFeedIterator( + /// ChangeFeedStartFrom.Beginning(feedRanges[0]), + /// options); /// while (feedIterator.HasMoreResults) /// { /// while (feedIterator.HasMoreResults) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index f264f13973..0b464750a7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos using System.IO; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index cb762ae666..4432ca8903 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.EmulatorTests.FeedRanges using System.Net; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -162,7 +163,9 @@ public async Task ChangeFeedIteratorCore_PartitionKey_ReadAll() ContainerInternal itemsCore = this.Container; ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( - ChangeFeedStartFrom.Beginning(new FeedRangePartitionKey(new PartitionKey(pkToRead)))) as ChangeFeedIteratorCore; + ChangeFeedStartFrom.Beginning( + FeedRange.FromPartitionKey( + new PartitionKey(pkToRead)))) as ChangeFeedIteratorCore; string continuation = null; while (feedIterator.HasMoreResults) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index 4808f2d193..936245a965 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Tests.FeedRange using System.Net; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Routing; using Microsoft.VisualStudio.TestTools.UnitTesting; From 3b224b0da56d4d07008aace70c23152e68590838 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Mon, 10 Aug 2020 10:12:26 -0700 Subject: [PATCH 16/19] fixed build issue --- .../Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs index 23f7ed385f..ca228c2a7d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Query/EndToEnd.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Cosmos.Json; From 7c5e7abdfbe7483fce001f77184007aacf39cd11 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Mon, 10 Aug 2020 10:58:55 -0700 Subject: [PATCH 17/19] fixed test build errors --- .../CosmosItemChangeFeedTests.cs | 1 + .../StandByFeedContinuationTokenTests.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs index dc6f9256de..dbffcac0b3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemChangeFeedTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Linq; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs index d745dd1296..1cc040d326 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StandByFeedContinuationTokenTests.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -224,7 +225,7 @@ public void ConstructorWithRangeGeneratesSingleQueue() public void ChangeFeedRequestOptions_ContinuationIsSet() { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); ChangeFeedStartFrom.ContinuationToken("something").Accept(visitor); Assert.AreEqual(expected: "something", actual: request.Headers.IfNoneMatch); @@ -235,7 +236,7 @@ public void ChangeFeedRequestOptions_ContinuationIsSet() public void ChangeFeedRequestOptions_StartFromNow() { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); ChangeFeedStartFrom.Now().Accept(visitor); Assert.AreEqual(expected: "*", request.Headers.IfNoneMatch); @@ -246,7 +247,7 @@ public void ChangeFeedRequestOptions_StartFromNow() public void ChangeFeedRequestOptions_StartFromBeginning() { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); ChangeFeedStartFrom.Beginning().Accept(visitor); Assert.IsNull(request.Headers.IfNoneMatch); @@ -257,7 +258,7 @@ public void ChangeFeedRequestOptions_StartFromBeginning() public void ChangeFeedRequestOptions_MaxItemSizeIsSet() { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); ChangeFeedRequestOptions requestOptions = new ChangeFeedRequestOptions() { PageSizeHint = 10, @@ -274,7 +275,7 @@ public void ChangeFeedRequestOptions_MaxItemSizeIsSet() public void ChangeFeedRequestOptions_AddsStartTime() { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); ChangeFeedStartFrom.Time(new DateTime(1985, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Accept(visitor); Assert.AreEqual( @@ -297,7 +298,7 @@ public void ChangeFeedRequestOptions_AddsFeedRange() foreach (ChangeFeedStartFrom from in froms) { RequestMessage request = new RequestMessage(); - PopulateStartFromRequestOptionVisitor visitor = new PopulateStartFromRequestOptionVisitor(request); + ChangeFeedStartFromRequestOptionPopulator visitor = new ChangeFeedStartFromRequestOptionPopulator(request); from.Accept(visitor); Assert.AreEqual( From 4ae6f31eb9cdee0eb5ae53f81c3eca89709fda2b Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Mon, 10 Aug 2020 11:44:51 -0700 Subject: [PATCH 18/19] more build fixes --- .../Contracts/ContractTests.cs | 1 + .../ChangeFeedResultSetIteratorTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs index bcaccbe7b1..a505b434e4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Contracts/ContractTests.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Contracts using Microsoft.Azure.Documents; using System.IO; using Newtonsoft.Json.Linq; + using Microsoft.Azure.Cosmos.ChangeFeed; [EmulatorTests.TestClass] public class ContractTests : BaseCosmosClientHelper diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index dac5521dfd..9d1e0a4148 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; + using Microsoft.Azure.Cosmos.ChangeFeed; [TestClass] public class ChangeFeedResultSetIteratorTests From a486d30ea1aad9a4b4281607b5796c8dfd3c5456 Mon Sep 17 00:00:00 2001 From: Brandon Chong Date: Mon, 10 Aug 2020 12:51:14 -0700 Subject: [PATCH 19/19] updated preview API json --- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 5 ----- .../Contracts/DotNetPreviewSDKAPI.json | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index f4756ea65f..a314effffb 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -64,11 +64,6 @@ internal SubStatusCodes SubStatusCode [CosmosKnownHeaderAttribute(HeaderName = HttpConstants.HttpHeaders.Continuation)] public virtual string ContinuationToken { get; internal set; } - internal void Add(object ifModifiedSince, string v) - { - throw new NotImplementedException(); - } - /// /// Gets the request charge for this request from the Azure Cosmos DB service. /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index ca92934d34..e8131410f3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -179,15 +179,15 @@ "FeedRange": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.FeedRange CreateFromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)": { + "Microsoft.Azure.Cosmos.FeedRange FromJsonString(System.String)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange CreateFromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FromJsonString(System.String)" }, - "Microsoft.Azure.Cosmos.FeedRange FromJsonString(System.String)": { + "Microsoft.Azure.Cosmos.FeedRange FromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FromJsonString(System.String)" + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FromPartitionKey(Microsoft.Azure.Cosmos.PartitionKey)" }, "System.String ToJsonString()": { "Type": "Method",