From a71d4743d5c09abdb2a7ec14c446e676e8960885 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 15 May 2024 22:27:30 -0400 Subject: [PATCH 001/145] initial checkin --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 7217993fd5..2a4ac2e83d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -6,9 +6,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; + using Antlr4.Runtime.Sharpen; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; @@ -29,6 +31,8 @@ public async Task Cleanup() await base.TestCleanup(); } + private static readonly Dictionary Bookmarks = new(); + [TestMethod] [Owner("philipthomas-MSFT")] [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + @@ -42,6 +46,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { + // Get the current feed range using 'context.Headers.PartitionKeyRangeId'. + + FeedRange currentFeedRange = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.GetFeedRangeByPartitionKeyRangeId( + context: context, + container: monitoredContainer, + cancellationToken: this.cancellationToken); + string id = default; string pk = default; string description = default; @@ -65,6 +76,27 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; + + // Does the 'change.Metadata.Lsn' belong to the current feed range. If it does, + // this means that it has been processed? + + if (!GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.Bookmarks.TryGetValue(lsn, out FeedRange feedRange)) + { + bool hasLsnProcessed = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.HasLsnProcessed( + container: monitoredContainer, + lsn: lsn, + feedRange: currentFeedRange); + + if (hasLsnProcessed) + { + // Bookmark the lsn and feedRange. + + GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.Bookmarks.Add( + key: lsn, + value: currentFeedRange); + } + } + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; } @@ -144,6 +176,63 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } } + private static bool HasLsnProcessed( + long lsn, + FeedRange feedRange, + ContainerInternal container) + { + if (feedRange is null) + { + throw new ArgumentNullException(nameof(feedRange)); + } + + if (container is null) + { + throw new ArgumentNullException(nameof(container)); + } + + Debug.WriteLine($"{nameof(lsn)}: {lsn}"); + Debug.WriteLine($"{nameof(feedRange)}: {feedRange}"); + + return default; + } + + private static FeedRange GetFeedRangeByPartitionKeyRangeId( + ChangeFeedProcessorContext context, + ContainerInternal container, + CancellationToken cancellationToken) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (container is null) + { + throw new ArgumentNullException(nameof(container)); + } + + Task> feedRangesTask = container.GetFeedRangesAsync(cancellationToken); + IReadOnlyList feedRanges = feedRangesTask.Result; + + foreach (FeedRange feedRange in feedRanges) + { + Task> pkRangeIdsTask = container.GetPartitionKeyRangesAsync( + feedRange: feedRange, + cancellationToken: cancellationToken); + IEnumerable pkRangeIds = pkRangeIdsTask.Result; + + if (pkRangeIds.Contains(context.Headers.PartitionKeyRangeId)) + { + Debug.WriteLine(feedRange.ToJsonString()); + + return feedRange; + } + } + + return default; + } + /// /// This is based on an issue located at . /// From 749c7ffd7be91ec8bfd7e5c888fc5a7850ac81b7 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 30 May 2024 11:55:23 -0400 Subject: [PATCH 002/145] feedRangeFetails added to headers as a tuple. may created a public type later --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 15 ++- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 9 ++ .../src/Routing/PartitionKeyRangeCache.cs | 11 +- ...orBuilderWithAllVersionsAndDeletesTests.cs | 108 ++++-------------- 4 files changed, 56 insertions(+), 87 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 738a1d23b7..769fbe358e 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -59,13 +59,15 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore Create( container: container, mode: mode, changeFeedStartFrom: startFrom, - options: requestOptions); + options: requestOptions, + feedRangeEpk: lease?.FeedRange is FeedRangeEpk epk ? epk : default); } private readonly CosmosClientContext clientContext; private readonly ChangeFeedRequestOptions changeFeedOptions; private readonly ChangeFeedMode mode; + private readonly FeedRangeEpk feedRangeEpk; private ChangeFeedStartFrom changeFeedStartFrom; private bool hasMoreResultsInternal; @@ -74,13 +76,15 @@ private ChangeFeedPartitionKeyResultSetIteratorCore( ContainerInternal container, ChangeFeedMode mode, ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedRequestOptions options) + ChangeFeedRequestOptions options, + FeedRangeEpk feedRangeEpk) { this.container = container ?? throw new ArgumentNullException(nameof(container)); this.mode = mode; this.changeFeedStartFrom = changeFeedStartFrom ?? throw new ArgumentNullException(nameof(changeFeedStartFrom)); this.clientContext = this.container.ClientContext; this.changeFeedOptions = options; + this.feedRangeEpk = feedRangeEpk; } public override bool HasMoreResults => this.hasMoreResultsInternal; @@ -145,6 +149,13 @@ public override async Task ReadNextAsync(ITrace trace, Cancella responseMessage.Headers.ContinuationToken = etag; this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)this.changeFeedStartFrom.FeedRange); + // Set the FeedRangeMinMax response header. + + if (this.feedRangeEpk != null) + { + responseMessage.Headers.FeedRangeDetails = (this.feedRangeEpk.Range.Min, this.feedRangeEpk.Range.Max, responseMessage.RequestMessage.DocumentServiceRequest.ResourceId); + } + return responseMessage; } } diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index 40f90c423a..f222949090 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -457,5 +457,14 @@ internal static SubStatusCodes GetSubStatusCodes(string value) return null; } + + /// + /// Gets the feed range details. + /// + public virtual (string Min, string Max, string CollectionRid) FeedRangeDetails + { + get; + internal set; + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index d849e4d3c0..6359a7fc5e 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -11,7 +11,6 @@ namespace Microsoft.Azure.Cosmos.Routing using System.Linq; using System.Net; using System.Text; - using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Common; using Microsoft.Azure.Cosmos.Core.Trace; @@ -81,6 +80,16 @@ public virtual async Task> TryGetOverlappingRan } } + public virtual async Task> TryGetOverlappingRangesAsync( + string collectionRid, + Range range, + long lsn, + ITrace trace, + bool forceRefresh = false) + { + return await Task.FromResult(default(IReadOnlyList)); + } + public virtual async Task TryGetPartitionKeyRangeByIdAsync( string collectionResourceId, string partitionKeyRangeId, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 2a4ac2e83d..ec224039a1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -12,7 +12,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Threading.Tasks; using Antlr4.Runtime.Sharpen; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json.Linq; [TestClass] @@ -31,7 +33,7 @@ public async Task Cleanup() await base.TestCleanup(); } - private static readonly Dictionary Bookmarks = new(); + private static readonly Dictionary> Bookmarks = new(); [TestMethod] [Owner("philipthomas-MSFT")] @@ -44,14 +46,15 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Exception exception = default; ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - // Get the current feed range using 'context.Headers.PartitionKeyRangeId'. + // Note(philipthomas): Get the current PartitionKeyRange using 'context.Headers.PartitionKeyRangeId'. - FeedRange currentFeedRange = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.GetFeedRangeByPartitionKeyRangeId( - context: context, - container: monitoredContainer, - cancellationToken: this.cancellationToken); + (string Min, string Max, string CollectionRid) = context.Headers.FeedRangeDetails; + + Debug.WriteLine($"{nameof(Min)}-> {Min}"); + Debug.WriteLine($"{nameof(Max)}-> {Max}"); + Debug.WriteLine($"{nameof(CollectionRid)}-> {CollectionRid}"); string id = default; string pk = default; @@ -76,28 +79,22 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; - // Does the 'change.Metadata.Lsn' belong to the current feed range. If it does, - // this means that it has been processed? + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - if (!GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.Bookmarks.TryGetValue(lsn, out FeedRange feedRange)) - { - bool hasLsnProcessed = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.HasLsnProcessed( - container: monitoredContainer, - lsn: lsn, - feedRange: currentFeedRange); - - if (hasLsnProcessed) - { - // Bookmark the lsn and feedRange. - - GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.Bookmarks.Add( - key: lsn, - value: currentFeedRange); - } - } + IReadOnlyList overlappingRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: CollectionRid, + range: new Documents.Routing.Range( + min: Min, + max: Max, + isMinInclusive: false, + isMaxInclusive: false), + lsn: lsn, + trace: NoOpTrace.Singleton, + forceRefresh: false); - bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + Debug.WriteLine($"{nameof(overlappingRanges)}-> {Newtonsoft.Json.JsonConvert.SerializeObject(overlappingRanges)}"); } Assert.IsNotNull(context.LeaseToken); @@ -140,7 +137,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - return Task.CompletedTask; + return; // Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) .WithLeaseContainer(this.LeaseContainer) @@ -176,63 +173,6 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } } - private static bool HasLsnProcessed( - long lsn, - FeedRange feedRange, - ContainerInternal container) - { - if (feedRange is null) - { - throw new ArgumentNullException(nameof(feedRange)); - } - - if (container is null) - { - throw new ArgumentNullException(nameof(container)); - } - - Debug.WriteLine($"{nameof(lsn)}: {lsn}"); - Debug.WriteLine($"{nameof(feedRange)}: {feedRange}"); - - return default; - } - - private static FeedRange GetFeedRangeByPartitionKeyRangeId( - ChangeFeedProcessorContext context, - ContainerInternal container, - CancellationToken cancellationToken) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (container is null) - { - throw new ArgumentNullException(nameof(container)); - } - - Task> feedRangesTask = container.GetFeedRangesAsync(cancellationToken); - IReadOnlyList feedRanges = feedRangesTask.Result; - - foreach (FeedRange feedRange in feedRanges) - { - Task> pkRangeIdsTask = container.GetPartitionKeyRangesAsync( - feedRange: feedRange, - cancellationToken: cancellationToken); - IEnumerable pkRangeIds = pkRangeIdsTask.Result; - - if (pkRangeIds.Contains(context.Headers.PartitionKeyRangeId)) - { - Debug.WriteLine(feedRange.ToJsonString()); - - return feedRange; - } - } - - return default; - } - /// /// This is based on an issue located at . /// From 158b26d1993d0027cbd7d99b8861c57158380bd0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 30 May 2024 12:17:12 -0400 Subject: [PATCH 003/145] FeedRangeDetail type in place of tuple --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 5 +- .../src/ChangeFeed/FeedRangeDetail.cs | 54 +++++++++++++++++++ Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 2 +- ...orBuilderWithAllVersionsAndDeletesTests.cs | 19 ++++--- 4 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 769fbe358e..9e813c3992 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -153,7 +153,10 @@ public override async Task ReadNextAsync(ITrace trace, Cancella if (this.feedRangeEpk != null) { - responseMessage.Headers.FeedRangeDetails = (this.feedRangeEpk.Range.Min, this.feedRangeEpk.Range.Max, responseMessage.RequestMessage.DocumentServiceRequest.ResourceId); + responseMessage.Headers.FeedRangeDetails = FeedRangeDetail.Create( + minInclusive: this.feedRangeEpk.Range.Min, + maxExclusive: this.feedRangeEpk.Range.Max, + collectionRid: responseMessage.RequestMessage.DocumentServiceRequest.ResourceId); } return responseMessage; diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs new file mode 100644 index 0000000000..130072068f --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs @@ -0,0 +1,54 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.ChangeFeed +{ + /// + /// The feed range details. + /// + public class FeedRangeDetail + { + /// + /// Gets the min inclusive. + /// + public string MinInclusive { get; private set; } + + /// + /// Gets the max exclusive. + /// + public string MaxExclusive { get; private set; } + + /// + /// Gets the collection resource id. + /// + public string CollectionRid { get; private set; } + + /// + /// Creates a new feed range detail. + /// + /// + /// + /// + /// A immutable feed range detail. + public static FeedRangeDetail Create(string minInclusive, string maxExclusive, string collectionRid) + { + return new FeedRangeDetail( + minInclusive: minInclusive, + maxExclusive: maxExclusive, + collectionRid: collectionRid); + } + /// + /// The construtor for the feed range detail. + /// + /// The minInclusive for the feed range. + /// The maxExclusive for the feed range. + /// The collection resource id for the feed range. + private FeedRangeDetail(string minInclusive, string maxExclusive, string collectionRid) + { + this.MinInclusive = minInclusive; + this.MaxExclusive = maxExclusive; + this.CollectionRid = collectionRid; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index f222949090..4c5b99f47e 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -461,7 +461,7 @@ internal static SubStatusCodes GetSubStatusCodes(string value) /// /// Gets the feed range details. /// - public virtual (string Min, string Max, string CollectionRid) FeedRangeDetails + public virtual ChangeFeed.FeedRangeDetail FeedRangeDetails { get; internal set; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index ec224039a1..60aa7da9bf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -10,11 +10,10 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Linq; using System.Threading; using System.Threading.Tasks; - using Antlr4.Runtime.Sharpen; + using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; using Newtonsoft.Json.Linq; [TestClass] @@ -48,13 +47,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - // Note(philipthomas): Get the current PartitionKeyRange using 'context.Headers.PartitionKeyRangeId'. + // Note(philipthomas): Get the current feed range minInclusive, maxExclusive and resourceId using 'context.Headers.FeedRangeDetails'. - (string Min, string Max, string CollectionRid) = context.Headers.FeedRangeDetails; + FeedRangeDetail feedRangeDetail = context.Headers.FeedRangeDetails; - Debug.WriteLine($"{nameof(Min)}-> {Min}"); - Debug.WriteLine($"{nameof(Max)}-> {Max}"); - Debug.WriteLine($"{nameof(CollectionRid)}-> {CollectionRid}"); + Debug.WriteLine($"{nameof(feedRangeDetail.MinInclusive)}-> {feedRangeDetail.MinInclusive}"); + Debug.WriteLine($"{nameof(feedRangeDetail.MaxExclusive)}-> {feedRangeDetail.MaxExclusive}"); + Debug.WriteLine($"{nameof(feedRangeDetail.CollectionRid)}-> {feedRangeDetail.CollectionRid}"); string id = default; string pk = default; @@ -84,10 +83,10 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Routing.PartitionKeyRangeCache partitionKeyRangeCache = await monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); IReadOnlyList overlappingRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: CollectionRid, + collectionRid: feedRangeDetail.CollectionRid, range: new Documents.Routing.Range( - min: Min, - max: Max, + min: feedRangeDetail.MinInclusive, + max: feedRangeDetail.MaxExclusive, isMinInclusive: false, isMaxInclusive: false), lsn: lsn, From 5472e18f2f994ffec58f6a61ae6427bbeb32096c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 3 Jun 2024 07:50:40 -0400 Subject: [PATCH 004/145] some tests but impl is incomplete --- .../FullFidelity/ChangeFeedMetadata.cs | 4 +- .../src/Routing/PartitionKeyRangeCache.cs | 67 ++++++- ...orBuilderWithAllVersionsAndDeletesTests.cs | 168 ++++++++++++++++-- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 16 +- 4 files changed, 228 insertions(+), 27 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index c5bd4642fa..aee454b505 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -67,7 +67,7 @@ public ChangeFeedMetadata( /// /// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents). /// - [JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling= NullValueHandling.Ignore)] - public bool IsTimeToLiveExpired { get; } + [JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling = NullValueHandling.Ignore)] + public bool TimeToLiveExpired { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index 6359a7fc5e..9170258c0a 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Cosmos.Routing using System.Net; using System.Text; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Common; using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Tracing; @@ -19,6 +20,7 @@ namespace Microsoft.Azure.Cosmos.Routing using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; using Microsoft.Azure.Documents.Routing; + using Newtonsoft.Json; internal class PartitionKeyRangeCache : IRoutingMapProvider, ICollectionRoutingMapCache { @@ -80,14 +82,73 @@ public virtual async Task> TryGetOverlappingRan } } + /// + /// Gets the overlapping ranges for a list of feed ranges. + /// + /// + /// + /// + /// public virtual async Task> TryGetOverlappingRangesAsync( string collectionRid, - Range range, - long lsn, + IReadOnlyList> ranges, + ITrace trace, + bool forceRefresh = false) + { + List partitionKeyRanges = new (); + + using (ITrace childTrace = trace.StartChild("Try Get Overlapping Ranges", TraceComponent.Routing, Tracing.TraceLevel.Info)) + { + Debug.Assert(ResourceId.TryParse(collectionRid, out ResourceId collectionRidParsed), "Could not parse CollectionRid from ResourceId."); + + foreach (Range range in ranges) + { + IReadOnlyList overlappingRanges = await this.TryGetOverlappingRangesAsync( + collectionRid: collectionRid, + range: range, + trace: childTrace, + forceRefresh: forceRefresh); + + Debug.Assert(overlappingRanges != null, $"There should always be overlapping ranges for {JsonConvert.SerializeObject(range)}."); + + partitionKeyRanges.AddRange(overlappingRanges); + } + } + + return partitionKeyRanges.AsReadOnly(); + } + + /// + /// Gets the overlapping ranges for a list of feed ranges. + /// + /// + /// + /// + /// + public virtual async Task> TryGetOverlappingRangesAsync( + string collectionRid, + PartitionKey partitionKey, ITrace trace, bool forceRefresh = false) { - return await Task.FromResult(default(IReadOnlyList)); + List partitionKeyRanges = new (); + + using (ITrace childTrace = trace.StartChild("Try Get Overlapping Ranges", TraceComponent.Routing, Tracing.TraceLevel.Info)) + { + Debug.Assert(ResourceId.TryParse(collectionRid, out ResourceId collectionRidParsed), "Could not parse CollectionRid from ResourceId."); + + CollectionRoutingMap routingMap = await this.TryLookupAsync( + collectionRid: collectionRid, + previousValue: null, + request: null, + trace: childTrace); + + Debug.WriteLine($"{nameof(routingMap)} -> {JsonConvert.SerializeObject(routingMap)}"); + } + + await Task.Delay(TimeSpan.FromSeconds(5)); + + return partitionKeyRanges.AsReadOnly(); } public virtual async Task TryGetPartitionKeyRangeByIdAsync( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 60aa7da9bf..9483480459 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -12,8 +12,10 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; [TestClass] @@ -47,17 +49,17 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - // Note(philipthomas): Get the current feed range minInclusive, maxExclusive and resourceId using 'context.Headers.FeedRangeDetails'. + // Note(philipthomas): Get the current feed range minInclusive, maxExclusive and resourceId using 'context.Headers.FeedRangeDetails'. - FeedRangeDetail feedRangeDetail = context.Headers.FeedRangeDetails; + FeedRangeDetail feedRangeDetail = context.Headers.FeedRangeDetails; - Debug.WriteLine($"{nameof(feedRangeDetail.MinInclusive)}-> {feedRangeDetail.MinInclusive}"); - Debug.WriteLine($"{nameof(feedRangeDetail.MaxExclusive)}-> {feedRangeDetail.MaxExclusive}"); - Debug.WriteLine($"{nameof(feedRangeDetail.CollectionRid)}-> {feedRangeDetail.CollectionRid}"); + Debug.WriteLine($"{nameof(feedRangeDetail.MinInclusive)}-> {feedRangeDetail.MinInclusive}"); + Debug.WriteLine($"{nameof(feedRangeDetail.MaxExclusive)}-> {feedRangeDetail.MaxExclusive}"); + Debug.WriteLine($"{nameof(feedRangeDetail.CollectionRid)}-> {feedRangeDetail.CollectionRid}"); - string id = default; - string pk = default; - string description = default; + string id = default; + string pk = default; + string description = default; foreach (ChangeFeedItem change in docs) { @@ -78,22 +80,28 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + //bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; Routing.PartitionKeyRangeCache partitionKeyRangeCache = await monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); IReadOnlyList overlappingRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: feedRangeDetail.CollectionRid, + collectionRid: feedRangeDetail.CollectionRid, range: new Documents.Routing.Range( min: feedRangeDetail.MinInclusive, max: feedRangeDetail.MaxExclusive, isMinInclusive: false, isMaxInclusive: false), - lsn: lsn, trace: NoOpTrace.Singleton, forceRefresh: false); Debug.WriteLine($"{nameof(overlappingRanges)}-> {Newtonsoft.Json.JsonConvert.SerializeObject(overlappingRanges)}"); + + _ = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: feedRangeDetail.CollectionRid, + partitionKey: new Documents.PartitionKey(pk), + trace: NoOpTrace.Singleton, + false); + } Assert.IsNotNull(context.LeaseToken); @@ -154,13 +162,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() await processor.StartAsync(); await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); + await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new Cosmos.PartitionKey("1")); await Task.Delay(1000); - await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); + await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new Cosmos.PartitionKey("1")); await Task.Delay(1000); - await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); + await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new Cosmos.PartitionKey("1")); bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); @@ -172,6 +180,137 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } } + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] + public async Task WhenADocumentIsCreatedAndTtlIsTrueTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + int partitionKey = 0; + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + Exception exception = default; + //////int processedDocCount = 0; + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + //////string id = default; + //////string pk = default; + //////string description = default; + + Console.WriteLine($"{nameof(docs)} -> {JsonConvert.SerializeObject(docs)}"); + //////processedDocCount += docs.Count(); + //////foreach (ChangeFeedItem change in docs) + //////{ + ////// if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) + ////// { + ////// id = change.Current.id.ToString(); + ////// pk = change.Current.pk.ToString(); + ////// description = change.Current.description.ToString(); + ////// } + ////// else + ////// { + ////// id = change.Previous.id.ToString(); + ////// pk = change.Previous.pk.ToString(); + ////// description = change.Previous.description.ToString(); + ////// } + + ////// ChangeFeedOperationType operationType = change.Metadata.OperationType; + ////// long previousLsn = change.Metadata.PreviousLsn; + ////// DateTime m = change.Metadata.ConflictResolutionTimestamp; + ////// long lsn = change.Metadata.Lsn; + ////// bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + //////} + + //////Debug.WriteLine($"{nameof(processedDocCount)} -> {processedDocCount}"); + + //////if (processedDocCount == 20) + //////{ + ////// allDocsProcessed.Set(); + //////} + + //////Assert.IsNotNull(context.LeaseToken); + //////Assert.IsNotNull(context.Diagnostics); + //////Assert.IsNotNull(context.Headers); + //////Assert.IsNotNull(context.Headers.Session); + //////Assert.IsTrue(context.Headers.RequestCharge > 0); + //////Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); + //////Assert.AreEqual(expected: 3, actual: docs.Count); + + //////ChangeFeedItem createChange = docs.ElementAt(0); + //////Assert.IsNotNull(createChange.Current); + //////Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); + //////Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); + //////Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); + //////Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); + //////Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); + //////Assert.IsNull(createChange.Previous); + + //////ChangeFeedItem replaceChange = docs.ElementAt(1); + //////Assert.IsNotNull(replaceChange.Current); + //////Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); + //////Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); + //////Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); + //////Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); + //////Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); + //////Assert.IsNull(replaceChange.Previous); + + //////ChangeFeedItem deleteChange = docs.ElementAt(2); + //////Assert.IsNull(deleteChange.Current.id); + //////Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); + //////Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); + //////Assert.IsNotNull(deleteChange.Previous); + //////Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); + //////Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); + //////Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); + + //////Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); + //////Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); + //////Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); + //////Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); + + return Task.CompletedTask; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + // Start the processor, insert 1 document to generate a checkpoint, modify it, and then delete it. + // 1 second delay between operations to get different timestamps. + + await processor.StartAsync(); + //////await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + foreach (int id in Enumerable.Range(0, 10)) + { + await monitoredContainer.CreateItemAsync(new { id = id.ToString(), pk = partitionKey, ttl = 1 }); + } + + //////await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "test 1", ttl = 1 }, partitionKey: new PartitionKey("1")); + //////await monitoredContainer.CreateItemAsync(new { id = "2", pk = "2", description = "test 2", ttl = 1 }, partitionKey: new PartitionKey("2")); + //////await monitoredContainer.CreateItemAsync(new { id = "3", pk = "3", description = "test 3", ttl = 1 }, partitionKey: new PartitionKey("3")); + + //////await Task.Delay(TimeSpan.FromSeconds(10)); + + //////bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + //////bool isStartOk = allDocsProcessed.WaitOne(TimeSpan.FromMinutes(5)); + + await Task.Delay(TimeSpan.FromSeconds(240)); + + await processor.StopAsync(); + //////Assert.IsTrue(isStartOk, "Timed out waiting for docs to process"); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + /// /// This is based on an issue located at . /// @@ -495,6 +634,7 @@ private async Task CreateMonitoredContainer(ChangeFeedMode ch if (changeFeedMode == ChangeFeedMode.AllVersionsAndDeletes) { properties.ChangeFeedPolicy.FullFidelityRetention = TimeSpan.FromMinutes(5); + properties.DefaultTimeToLive = -1; } ContainerResponse response = await this.database.CreateContainerAsync(properties, 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 a43d86faf5..6f59aec6b2 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 @@ -975,7 +975,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges.ElementAtOrDefault(1); @@ -989,7 +989,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); break; } @@ -1054,7 +1054,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLsn); - Assert.IsFalse(firstCreateOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(firstCreateOperation.Metadata.TimeToLiveExpired); ChangeFeedItem createOperation = resources[1]; @@ -1067,7 +1067,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = resources[2]; @@ -1080,7 +1080,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); ChangeFeedItem deleteOperation = resources[3]; @@ -1163,7 +1163,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges[1]; @@ -1177,7 +1177,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); ChangeFeedItem deleteOperation = itemChanges[2]; @@ -1186,7 +1186,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); break; } From 5e3f27742b6c8cf3d404b596727688f814d52891 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 2 Jul 2024 10:11:02 -0400 Subject: [PATCH 005/145] merge --- .../QueryClient/ContainerQueryProperties.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs index 18d8c59a0b..945386e092 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs @@ -1,39 +1,39 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Query.Core.QueryClient -{ - using System.Collections.Generic; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Routing; - - internal readonly struct ContainerQueryProperties - { - public ContainerQueryProperties( - string resourceId, - IReadOnlyList> effectivePartitionKeyRanges, +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Query.Core.QueryClient +{ + using System.Collections.Generic; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + + internal readonly struct ContainerQueryProperties + { + public ContainerQueryProperties( + string resourceId, + IReadOnlyList> effectivePartitionKeyRanges, PartitionKeyDefinition partitionKeyDefinition, - Cosmos.VectorEmbeddingPolicy vectorEmbeddingPolicy, - Cosmos.GeospatialType geospatialType) - { - this.ResourceId = resourceId; - this.EffectiveRangesForPartitionKey = effectivePartitionKeyRanges; + Cosmos.VectorEmbeddingPolicy vectorEmbeddingPolicy, + Cosmos.GeospatialType geospatialType) + { + this.ResourceId = resourceId; + this.EffectiveRangesForPartitionKey = effectivePartitionKeyRanges; this.PartitionKeyDefinition = partitionKeyDefinition; - this.VectorEmbeddingPolicy = vectorEmbeddingPolicy; - this.GeospatialType = geospatialType; - } - - public string ResourceId { get; } - - //A PartitionKey has one range when it is a full PartitionKey value. - //It can span many it is a prefix PartitionKey for a sub-partitioned container. - public IReadOnlyList> EffectiveRangesForPartitionKey { get; } + this.VectorEmbeddingPolicy = vectorEmbeddingPolicy; + this.GeospatialType = geospatialType; + } + + public string ResourceId { get; } + + //A PartitionKey has one range when it is a full PartitionKey value. + //It can span many it is a prefix PartitionKey for a sub-partitioned container. + public IReadOnlyList> EffectiveRangesForPartitionKey { get; } public PartitionKeyDefinition PartitionKeyDefinition { get; } - public Cosmos.VectorEmbeddingPolicy VectorEmbeddingPolicy { get; } + public Cosmos.VectorEmbeddingPolicy VectorEmbeddingPolicy { get; } - public Cosmos.GeospatialType GeospatialType { get; } - } + public Cosmos.GeospatialType GeospatialType { get; } + } } \ No newline at end of file From 02b220ea12fa6e79e987611611c4d8b0fbbdb826 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 2 Jul 2024 10:42:15 -0400 Subject: [PATCH 006/145] revert after bad merge --- .../QueryClient/ContainerQueryProperties.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs index 945386e092..18d8c59a0b 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/ContainerQueryProperties.cs @@ -1,39 +1,39 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Query.Core.QueryClient -{ - using System.Collections.Generic; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Routing; - - internal readonly struct ContainerQueryProperties - { - public ContainerQueryProperties( - string resourceId, - IReadOnlyList> effectivePartitionKeyRanges, +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Query.Core.QueryClient +{ + using System.Collections.Generic; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + + internal readonly struct ContainerQueryProperties + { + public ContainerQueryProperties( + string resourceId, + IReadOnlyList> effectivePartitionKeyRanges, PartitionKeyDefinition partitionKeyDefinition, - Cosmos.VectorEmbeddingPolicy vectorEmbeddingPolicy, - Cosmos.GeospatialType geospatialType) - { - this.ResourceId = resourceId; - this.EffectiveRangesForPartitionKey = effectivePartitionKeyRanges; + Cosmos.VectorEmbeddingPolicy vectorEmbeddingPolicy, + Cosmos.GeospatialType geospatialType) + { + this.ResourceId = resourceId; + this.EffectiveRangesForPartitionKey = effectivePartitionKeyRanges; this.PartitionKeyDefinition = partitionKeyDefinition; - this.VectorEmbeddingPolicy = vectorEmbeddingPolicy; - this.GeospatialType = geospatialType; - } - - public string ResourceId { get; } - - //A PartitionKey has one range when it is a full PartitionKey value. - //It can span many it is a prefix PartitionKey for a sub-partitioned container. - public IReadOnlyList> EffectiveRangesForPartitionKey { get; } + this.VectorEmbeddingPolicy = vectorEmbeddingPolicy; + this.GeospatialType = geospatialType; + } + + public string ResourceId { get; } + + //A PartitionKey has one range when it is a full PartitionKey value. + //It can span many it is a prefix PartitionKey for a sub-partitioned container. + public IReadOnlyList> EffectiveRangesForPartitionKey { get; } public PartitionKeyDefinition PartitionKeyDefinition { get; } - public Cosmos.VectorEmbeddingPolicy VectorEmbeddingPolicy { get; } + public Cosmos.VectorEmbeddingPolicy VectorEmbeddingPolicy { get; } - public Cosmos.GeospatialType GeospatialType { get; } - } + public Cosmos.GeospatialType GeospatialType { get; } + } } \ No newline at end of file From ae7ea264f01b16c8baf921d03a7e091379aa4ca3 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 2 Jul 2024 10:43:14 -0400 Subject: [PATCH 007/145] revert after a bad merge --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 181 +----------------- 1 file changed, 9 insertions(+), 172 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 6301630f16..0479bbb353 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Linq; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -33,8 +32,6 @@ public async Task Cleanup() await base.TestCleanup(); } - private static readonly Dictionary> Bookmarks = new(); - [TestMethod] [Timeout(300000)] [TestCategory("LongRunning")] @@ -155,19 +152,11 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Exception exception = default; ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - // Note(philipthomas): Get the current feed range minInclusive, maxExclusive and resourceId using 'context.Headers.FeedRangeDetails'. - - FeedRangeDetail feedRangeDetail = context.Headers.FeedRangeDetails; - - Debug.WriteLine($"{nameof(feedRangeDetail.MinInclusive)}-> {feedRangeDetail.MinInclusive}"); - Debug.WriteLine($"{nameof(feedRangeDetail.MaxExclusive)}-> {feedRangeDetail.MaxExclusive}"); - Debug.WriteLine($"{nameof(feedRangeDetail.CollectionRid)}-> {feedRangeDetail.CollectionRid}"); - - string id = default; - string pk = default; - string description = default; + string id = default; + string pk = default; + string description = default; foreach (ChangeFeedItem change in docs) { @@ -188,28 +177,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; - //bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; - - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - - IReadOnlyList overlappingRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: feedRangeDetail.CollectionRid, - range: new Documents.Routing.Range( - min: feedRangeDetail.MinInclusive, - max: feedRangeDetail.MaxExclusive, - isMinInclusive: false, - isMaxInclusive: false), - trace: NoOpTrace.Singleton, - forceRefresh: false); - - Debug.WriteLine($"{nameof(overlappingRanges)}-> {Newtonsoft.Json.JsonConvert.SerializeObject(overlappingRanges)}"); - - _ = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: feedRangeDetail.CollectionRid, - partitionKey: new Documents.PartitionKey(pk), - trace: NoOpTrace.Singleton, - false); - + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; } Assert.IsNotNull(context.LeaseToken); @@ -252,7 +220,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - return; // Task.CompletedTask; + return Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) .WithLeaseContainer(this.LeaseContainer) @@ -270,13 +238,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() await processor.StartAsync(); await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new Cosmos.PartitionKey("1")); + await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); await Task.Delay(1000); - await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new Cosmos.PartitionKey("1")); + await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); await Task.Delay(1000); - await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new Cosmos.PartitionKey("1")); + await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); @@ -288,137 +256,6 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } } - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + - "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] - public async Task WhenADocumentIsCreatedAndTtlIsTrueTestsAsync() - { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - int partitionKey = 0; - ManualResetEvent allDocsProcessed = new ManualResetEvent(false); - Exception exception = default; - //////int processedDocCount = 0; - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - //////string id = default; - //////string pk = default; - //////string description = default; - - Console.WriteLine($"{nameof(docs)} -> {JsonConvert.SerializeObject(docs)}"); - //////processedDocCount += docs.Count(); - //////foreach (ChangeFeedItem change in docs) - //////{ - ////// if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) - ////// { - ////// id = change.Current.id.ToString(); - ////// pk = change.Current.pk.ToString(); - ////// description = change.Current.description.ToString(); - ////// } - ////// else - ////// { - ////// id = change.Previous.id.ToString(); - ////// pk = change.Previous.pk.ToString(); - ////// description = change.Previous.description.ToString(); - ////// } - - ////// ChangeFeedOperationType operationType = change.Metadata.OperationType; - ////// long previousLsn = change.Metadata.PreviousLsn; - ////// DateTime m = change.Metadata.ConflictResolutionTimestamp; - ////// long lsn = change.Metadata.Lsn; - ////// bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; - //////} - - //////Debug.WriteLine($"{nameof(processedDocCount)} -> {processedDocCount}"); - - //////if (processedDocCount == 20) - //////{ - ////// allDocsProcessed.Set(); - //////} - - //////Assert.IsNotNull(context.LeaseToken); - //////Assert.IsNotNull(context.Diagnostics); - //////Assert.IsNotNull(context.Headers); - //////Assert.IsNotNull(context.Headers.Session); - //////Assert.IsTrue(context.Headers.RequestCharge > 0); - //////Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); - //////Assert.AreEqual(expected: 3, actual: docs.Count); - - //////ChangeFeedItem createChange = docs.ElementAt(0); - //////Assert.IsNotNull(createChange.Current); - //////Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); - //////Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); - //////Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); - //////Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - //////Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); - //////Assert.IsNull(createChange.Previous); - - //////ChangeFeedItem replaceChange = docs.ElementAt(1); - //////Assert.IsNotNull(replaceChange.Current); - //////Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); - //////Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); - //////Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); - //////Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - //////Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); - //////Assert.IsNull(replaceChange.Previous); - - //////ChangeFeedItem deleteChange = docs.ElementAt(2); - //////Assert.IsNull(deleteChange.Current.id); - //////Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - //////Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); - //////Assert.IsNotNull(deleteChange.Previous); - //////Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); - //////Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); - //////Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); - - //////Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); - //////Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); - //////Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); - //////Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - - return Task.CompletedTask; - }) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }) - .Build(); - - // Start the processor, insert 1 document to generate a checkpoint, modify it, and then delete it. - // 1 second delay between operations to get different timestamps. - - await processor.StartAsync(); - //////await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - foreach (int id in Enumerable.Range(0, 10)) - { - await monitoredContainer.CreateItemAsync(new { id = id.ToString(), pk = partitionKey, ttl = 1 }); - } - - //////await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "test 1", ttl = 1 }, partitionKey: new PartitionKey("1")); - //////await monitoredContainer.CreateItemAsync(new { id = "2", pk = "2", description = "test 2", ttl = 1 }, partitionKey: new PartitionKey("2")); - //////await monitoredContainer.CreateItemAsync(new { id = "3", pk = "3", description = "test 3", ttl = 1 }, partitionKey: new PartitionKey("3")); - - //////await Task.Delay(TimeSpan.FromSeconds(10)); - - //////bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - //////bool isStartOk = allDocsProcessed.WaitOne(TimeSpan.FromMinutes(5)); - - await Task.Delay(TimeSpan.FromSeconds(240)); - - await processor.StopAsync(); - //////Assert.IsTrue(isStartOk, "Timed out waiting for docs to process"); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - /// /// This is based on an issue located at . /// From 003da7e862e072f982e97d72892f81aeed2c8173 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 2 Jul 2024 10:44:11 -0400 Subject: [PATCH 008/145] revert after a bad merge --- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 6f59aec6b2..a43d86faf5 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 @@ -975,7 +975,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges.ElementAtOrDefault(1); @@ -989,7 +989,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; } @@ -1054,7 +1054,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLsn); - //Assert.IsFalse(firstCreateOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(firstCreateOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem createOperation = resources[1]; @@ -1067,7 +1067,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = resources[2]; @@ -1080,7 +1080,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = resources[3]; @@ -1163,7 +1163,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - //Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges[1]; @@ -1177,7 +1177,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = itemChanges[2]; @@ -1186,7 +1186,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); - //Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; } From 4af2d9174b8fb38ce22d11aa89c20b7cf1395dea Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Mon, 8 Jul 2024 13:31:03 -0400 Subject: [PATCH 009/145] changes --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 1 - .../src/Routing/IRoutingMapProvider.cs | 34 ++++++- .../src/Routing/PartitionKeyRangeCache.cs | 94 ++++++++++++------- .../IRoutingMapProviderExtensionsTest.cs | 28 +++++- 4 files changed, 117 insertions(+), 40 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 32d2778417..da159c1404 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.LeaseManagement; - using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; diff --git a/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs b/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs index dfc5662a29..b7a34c82af 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs @@ -23,7 +23,39 @@ internal interface IRoutingMapProvider /// The trace. /// Whether forcefully refreshing the routing map is necessary /// List of effective partition key ranges for a collection or null if collection doesn't exist. - Task> TryGetOverlappingRangesAsync(string collectionResourceId, Range range, ITrace trace, bool forceRefresh = false); + Task> TryGetOverlappingRangesAsync(string collectionResourceId, Range range, ITrace trace, bool forceRefresh = false); + + /// + /// Returns list of effective partition key ranges for a collection. + /// + /// + /// + /// + /// + /// + /// List of effective partition key ranges for a collection or null if collection doesn't exist. + Task> TryGetOverlappingRangesAsync( + string collectionResourceId, + IList> ranges, + PartitionKey partitionKey, + ITrace trace, + bool forceRefresh = false); + + /// + /// Returns list of effective partition key ranges for a collection. + /// + /// + /// + /// + /// + /// + /// List of effective partition key ranges for a collection or null if collection doesn't exist. + Task> TryGetOverlappingRangesAsync( + string collectionResourceId, + IList> ranges, + FeedRange feedRange, + ITrace trace, + bool forceRefresh = false); Task TryGetPartitionKeyRangeByIdAsync(string collectionResourceId, string partitionKeyRangeId, ITrace trace, bool forceRefresh = false); } diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index 9170258c0a..21f9bfd8f7 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -14,7 +14,8 @@ namespace Microsoft.Azure.Cosmos.Routing using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Common; - using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; using Microsoft.Azure.Documents; @@ -116,40 +117,7 @@ public virtual async Task> TryGetOverlappingRan } return partitionKeyRanges.AsReadOnly(); - } - - /// - /// Gets the overlapping ranges for a list of feed ranges. - /// - /// - /// - /// - /// - public virtual async Task> TryGetOverlappingRangesAsync( - string collectionRid, - PartitionKey partitionKey, - ITrace trace, - bool forceRefresh = false) - { - List partitionKeyRanges = new (); - - using (ITrace childTrace = trace.StartChild("Try Get Overlapping Ranges", TraceComponent.Routing, Tracing.TraceLevel.Info)) - { - Debug.Assert(ResourceId.TryParse(collectionRid, out ResourceId collectionRidParsed), "Could not parse CollectionRid from ResourceId."); - - CollectionRoutingMap routingMap = await this.TryLookupAsync( - collectionRid: collectionRid, - previousValue: null, - request: null, - trace: childTrace); - - Debug.WriteLine($"{nameof(routingMap)} -> {JsonConvert.SerializeObject(routingMap)}"); - } - - await Task.Delay(TimeSpan.FromSeconds(5)); - - return partitionKeyRanges.AsReadOnly(); - } + } public virtual async Task TryGetPartitionKeyRangeByIdAsync( string collectionResourceId, @@ -392,6 +360,60 @@ private async Task ExecutePartitionKeyRangeReadChangeFe } } } - } + } + + /// + /// Gets the overlapping ranges for a list of feed ranges. + /// + /// + /// + /// + /// + /// + public virtual async Task> TryGetOverlappingRangesAsync( + string collectionRid, + IList> ranges, + Cosmos.PartitionKey partitionKey, + ITrace trace, + bool forceRefresh = false) + { + List logicalPartitionKeyRanges = await this.TryGetOverlappingRangesAsync( + collectionRid, + ranges, + trace, + forceRefresh); + + // look at all the bookmarks, does the feed range of the bookmark overlap with the feed range (min, max) of the logical partition. + // is it in between the single epk hash value + // single partition key of the change. + + // get the partition key for the feed range. + + return logicalPartitionKeyRanges.AsReadOnly(); + } + + /// + /// Gets the overlapping ranges for a list of feed ranges. + /// + /// + /// + /// + /// + /// + public virtual async Task> TryGetOverlappingRangesAsync( + string collectionRid, + IList> ranges, + FeedRange feedRange, + ITrace trace, + bool forceRefresh = false) + { + List partitionKeyRanges = await this.TryGetOverlappingRangesAsync( + collectionRid, + ranges, + trace, + forceRefresh); + + return partitionKeyRanges.AsReadOnly(); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs index 7cbffaf230..9f4650a462 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs @@ -36,8 +36,32 @@ public Task> TryGetOverlappingRangesAsync( bool forceRefresh = false) { return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); - } - + } + + public Task> TryGetOverlappingRangesAsync( + string collectionResourceId, + IList> ranges, + PartitionKey partitionKey, + ITrace trace, + bool forceRefresh = false) + { + Range range = default; + + return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); + } + + public Task> TryGetOverlappingRangesAsync( + string collectionResourceId, + IList> ranges, + Cosmos.FeedRange feedRange, + ITrace trace, + bool forceRefresh = false) + { + Range range = default; + + return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); + } + public Task TryGetPartitionKeyRangeByIdAsync( string collectionResourceId, string partitionKeyRangeId, From 96262c06fff9cfbaa52888a7567a8acb973fe205 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Mon, 15 Jul 2024 16:08:06 -0400 Subject: [PATCH 010/145] more test to come --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 8 +- .../src/ChangeFeed/FeedRangeDetail.cs | 34 +++--- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 4 +- .../src/Resource/Container/Container.cs | 19 +++- .../src/Resource/Container/ContainerCore.cs | 78 ++++++++++++-- .../Resource/Container/ContainerInlineCore.cs | 22 +++- .../src/Routing/IRoutingMapProvider.cs | 34 +----- .../src/Routing/PartitionKeyRangeCache.cs | 98 +---------------- .../CosmosContainerTests.cs | 101 ++++++++++++++++-- .../IRoutingMapProviderExtensionsTest.cs | 28 +---- 10 files changed, 231 insertions(+), 195 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index da159c1404..2bad4f4ffc 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed { - using System; + using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -148,12 +148,12 @@ public override async Task ReadNextAsync(ITrace trace, Cancella if (this.feedRangeEpk != null) { responseMessage.Headers.FeedRangeDetails = FeedRangeDetail.Create( - minInclusive: this.feedRangeEpk.Range.Min, - maxExclusive: this.feedRangeEpk.Range.Max, + feedRange: FeedRangeEpk.FromJsonString(this.feedRangeEpk.ToJsonString()), collectionRid: responseMessage.RequestMessage.DocumentServiceRequest.ResourceId); } return responseMessage; - } + } + } } diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs index 130072068f..297eae919d 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs @@ -7,47 +7,39 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed /// /// The feed range details. /// - public class FeedRangeDetail + internal class FeedRangeDetail { /// /// Gets the min inclusive. /// - public string MinInclusive { get; private set; } - - /// - /// Gets the max exclusive. - /// - public string MaxExclusive { get; private set; } + public FeedRange FeedRange { get; private set; } /// /// Gets the collection resource id. /// - public string CollectionRid { get; private set; } - + public string CollectionRid { get; private set; } + /// /// Creates a new feed range detail. - /// - /// - /// + /// + /// /// /// A immutable feed range detail. - public static FeedRangeDetail Create(string minInclusive, string maxExclusive, string collectionRid) + public static FeedRangeDetail Create(FeedRange feedRange, string collectionRid) { return new FeedRangeDetail( - minInclusive: minInclusive, - maxExclusive: maxExclusive, + feedRange: feedRange, collectionRid: collectionRid); - } + } + /// /// The construtor for the feed range detail. /// - /// The minInclusive for the feed range. - /// The maxExclusive for the feed range. + /// The minInclusive for the feed range. /// The collection resource id for the feed range. - private FeedRangeDetail(string minInclusive, string maxExclusive, string collectionRid) + private FeedRangeDetail(FeedRange feedRange, string collectionRid) { - this.MinInclusive = minInclusive; - this.MaxExclusive = maxExclusive; + this.FeedRange = feedRange; this.CollectionRid = collectionRid; } } diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index 4c5b99f47e..9784180133 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -461,10 +461,10 @@ internal static SubStatusCodes GetSubStatusCodes(string value) /// /// Gets the feed range details. /// - public virtual ChangeFeed.FeedRangeDetail FeedRangeDetails + internal virtual ChangeFeed.FeedRangeDetail FeedRangeDetails { get; - internal set; + set; } } } \ 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 4aeef068ff..b2941a6c3b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1650,7 +1650,24 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( /// An instance of public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( string processorName, - ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); + + /// + /// Takes a given list of ranges and find overlapping ranges for the given partition key. + /// + /// A given partition key. + /// A given list of ranges. + /// + /// A list of overlapping ranges for the the given partition key. + public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); + + /// + /// Takes a given list of ranges and find overlapping ranges for the given feed range. + /// + /// A given feed range. + /// A given list of ranges. + /// A list of overlapping ranges for the the given feed range epk. + public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); #if PREVIEW /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index d3a30a4aff..3a1e80916b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -6,16 +6,13 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; - using System.IO; + using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed; - using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; - using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Pagination; - using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; @@ -256,7 +253,7 @@ public Task ReplaceContainerStreamAsync( requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken); - } + } public async Task> GetFeedRangesAsync( ITrace trace, @@ -699,6 +696,75 @@ public override FeedIterator GetChangeFeedIteratorWithQuery( return new FeedIteratorCore( changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); - } + } + + public override async Task> FindOverlappingRangesAsync( + Cosmos.PartitionKey partitionKey, + IReadOnlyList feedRanges, + CancellationToken cancellationToken = default) + { + List overlappingRanges = new (); + + foreach (Range range in ContainerCore.ConvertToRange(feedRanges)) + { + if (Range.CheckOverlapping( + range1: await this.ConvertToRangeAsync( + partitionKey: partitionKey, + cancellationToken: cancellationToken), + range2: range)) + { + overlappingRanges.Add(new FeedRangeEpk(range)); + } + } + + return overlappingRanges; + } + + public override IReadOnlyList FindOverlappingRanges( + Cosmos.FeedRange feedRange, + IReadOnlyList feedRanges) + { + List overlappingRanges = new List(); + + foreach (Range range in ContainerCore.ConvertToRange(feedRanges)) + { + if (Range.CheckOverlapping( + range1: ContainerCore.ConvertToRange(feedRange), + range2: range)) + { + overlappingRanges.Add(new FeedRangeEpk(range)); + } + } + + return overlappingRanges; + } + + private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) + { + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + string effectivePartitionKeyString = partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition); + CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken); + PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString); + + return partitionKeyRange.ToRange(); + } + + private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) + { + foreach (FeedRange fromFeedRange in fromFeedRanges) + { + yield return ContainerCore.ConvertToRange(fromFeedRange); + } + } + + private static Range ConvertToRange(FeedRange fromFeedRange) + { + if (fromFeedRange is not FeedRangeEpk feedRangeEpk) + { + return default; + } + + return feedRangeEpk.Range; + } } } \ 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 f3153a33cf..c62da82191 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -659,6 +659,26 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( requestOptions: requestOptions, task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), openTelemetry: (response) => new OpenTelemetryResponse(response)); - } + } + + public override async Task> FindOverlappingRangesAsync( + Cosmos.PartitionKey partitionKey, + IReadOnlyList feedRanges, + CancellationToken cancellationToken = default) + { + return await base.FindOverlappingRangesAsync( + partitionKey: partitionKey, + feedRanges: feedRanges, + cancellationToken: cancellationToken); + } + + public override IReadOnlyList FindOverlappingRanges( + Cosmos.FeedRange feedRange, + IReadOnlyList feedRanges) + { + return base.FindOverlappingRanges( + feedRange: feedRange, + feedRanges: feedRanges); + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs b/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs index b7a34c82af..dfc5662a29 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/IRoutingMapProvider.cs @@ -23,39 +23,7 @@ internal interface IRoutingMapProvider /// The trace. /// Whether forcefully refreshing the routing map is necessary /// List of effective partition key ranges for a collection or null if collection doesn't exist. - Task> TryGetOverlappingRangesAsync(string collectionResourceId, Range range, ITrace trace, bool forceRefresh = false); - - /// - /// Returns list of effective partition key ranges for a collection. - /// - /// - /// - /// - /// - /// - /// List of effective partition key ranges for a collection or null if collection doesn't exist. - Task> TryGetOverlappingRangesAsync( - string collectionResourceId, - IList> ranges, - PartitionKey partitionKey, - ITrace trace, - bool forceRefresh = false); - - /// - /// Returns list of effective partition key ranges for a collection. - /// - /// - /// - /// - /// - /// - /// List of effective partition key ranges for a collection or null if collection doesn't exist. - Task> TryGetOverlappingRangesAsync( - string collectionResourceId, - IList> ranges, - FeedRange feedRange, - ITrace trace, - bool forceRefresh = false); + Task> TryGetOverlappingRangesAsync(string collectionResourceId, Range range, ITrace trace, bool forceRefresh = false); Task TryGetPartitionKeyRangeByIdAsync(string collectionResourceId, string partitionKeyRangeId, ITrace trace, bool forceRefresh = false); } diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index 21f9bfd8f7..d849e4d3c0 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -11,17 +11,15 @@ namespace Microsoft.Azure.Cosmos.Routing using System.Linq; using System.Net; using System.Text; + using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Common; - using Microsoft.Azure.Cosmos.Core.Trace; - using Microsoft.Azure.Cosmos.Linq; + using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; using Microsoft.Azure.Documents.Routing; - using Newtonsoft.Json; internal class PartitionKeyRangeCache : IRoutingMapProvider, ICollectionRoutingMapCache { @@ -83,42 +81,6 @@ public virtual async Task> TryGetOverlappingRan } } - /// - /// Gets the overlapping ranges for a list of feed ranges. - /// - /// - /// - /// - /// - public virtual async Task> TryGetOverlappingRangesAsync( - string collectionRid, - IReadOnlyList> ranges, - ITrace trace, - bool forceRefresh = false) - { - List partitionKeyRanges = new (); - - using (ITrace childTrace = trace.StartChild("Try Get Overlapping Ranges", TraceComponent.Routing, Tracing.TraceLevel.Info)) - { - Debug.Assert(ResourceId.TryParse(collectionRid, out ResourceId collectionRidParsed), "Could not parse CollectionRid from ResourceId."); - - foreach (Range range in ranges) - { - IReadOnlyList overlappingRanges = await this.TryGetOverlappingRangesAsync( - collectionRid: collectionRid, - range: range, - trace: childTrace, - forceRefresh: forceRefresh); - - Debug.Assert(overlappingRanges != null, $"There should always be overlapping ranges for {JsonConvert.SerializeObject(range)}."); - - partitionKeyRanges.AddRange(overlappingRanges); - } - } - - return partitionKeyRanges.AsReadOnly(); - } - public virtual async Task TryGetPartitionKeyRangeByIdAsync( string collectionResourceId, string partitionKeyRangeId, @@ -360,60 +322,6 @@ private async Task ExecutePartitionKeyRangeReadChangeFe } } } - } - - /// - /// Gets the overlapping ranges for a list of feed ranges. - /// - /// - /// - /// - /// - /// - public virtual async Task> TryGetOverlappingRangesAsync( - string collectionRid, - IList> ranges, - Cosmos.PartitionKey partitionKey, - ITrace trace, - bool forceRefresh = false) - { - List logicalPartitionKeyRanges = await this.TryGetOverlappingRangesAsync( - collectionRid, - ranges, - trace, - forceRefresh); - - // look at all the bookmarks, does the feed range of the bookmark overlap with the feed range (min, max) of the logical partition. - // is it in between the single epk hash value - // single partition key of the change. - - // get the partition key for the feed range. - - return logicalPartitionKeyRanges.AsReadOnly(); - } - - /// - /// Gets the overlapping ranges for a list of feed ranges. - /// - /// - /// - /// - /// - /// - public virtual async Task> TryGetOverlappingRangesAsync( - string collectionRid, - IList> ranges, - FeedRange feedRange, - ITrace trace, - bool forceRefresh = false) - { - List partitionKeyRanges = await this.TryGetOverlappingRangesAsync( - collectionRid, - ranges, - trace, - forceRefresh); - - return partitionKeyRanges.AsReadOnly(); - } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index f3befc588b..8c67ee3f19 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -6,18 +6,17 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; using System.Collections.Generic; - using System.Collections.ObjectModel; + using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; - using HttpConstants = Microsoft.Azure.Documents.HttpConstants; - using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - + using Newtonsoft.Json.Linq; + [TestClass] public class CosmosContainerTests { @@ -1783,6 +1782,96 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsFalse(containerCore.LinkUri.ToString().StartsWith("/")); Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString()); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task TestFindOverlappingRangesByPartitionKeyAsync() + { + Container container = default; + + try + { + PartitionKey partitionKey = new PartitionKey("GA"); + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + await container.CreateItemAsync(item: new { id = Guid.NewGuid().ToString(), pk = "GA" }, partitionKey: partitionKey); + + Documents.Routing.Range range = new Documents.Routing.Range("AA", "BB", true, false); + FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); + string representation = feedRangeEpk.ToJsonString(); + + List ranges = new List() + { + FeedRange.FromJsonString(representation), + }; + + IReadOnlyList results = await container.FindOverlappingRangesAsync( + partitionKey: partitionKey, + feedRanges: ranges); + + Assert.IsNotNull(results); + } + catch (Exception ex) + { + Logger.LogLine($"{ex}"); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task TestFindOverlappingRangesByFeedRangeAsync() + { + Container container = default; + + try + { + PartitionKey partitionKey = new PartitionKey("GA"); + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + await container.CreateItemAsync(item: new { id = Guid.NewGuid().ToString(), pk = "GA" }, partitionKey: partitionKey); + + Documents.Routing.Range range = new Documents.Routing.Range("AA", "BB", true, false); + FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); + string representation = feedRangeEpk.ToJsonString(); + + List ranges = new List() + { + FeedRange.FromJsonString(representation), + }; + + IReadOnlyList results = container.FindOverlappingRanges( + feedRange: FeedRange.FromJsonString(FeedRangeEpk.FullRange.ToJsonString()), + feedRanges: ranges); + + Assert.IsNotNull(results); + } + catch (Exception ex) + { + Logger.LogLine($"{ex}"); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs index 9f4650a462..7cbffaf230 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Routing/IRoutingMapProviderExtensionsTest.cs @@ -36,32 +36,8 @@ public Task> TryGetOverlappingRangesAsync( bool forceRefresh = false) { return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); - } - - public Task> TryGetOverlappingRangesAsync( - string collectionResourceId, - IList> ranges, - PartitionKey partitionKey, - ITrace trace, - bool forceRefresh = false) - { - Range range = default; - - return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); - } - - public Task> TryGetOverlappingRangesAsync( - string collectionResourceId, - IList> ranges, - Cosmos.FeedRange feedRange, - ITrace trace, - bool forceRefresh = false) - { - Range range = default; - - return Task.FromResult(this.routingMap.GetOverlappingRanges(range)); - } - + } + public Task TryGetPartitionKeyRangeByIdAsync( string collectionResourceId, string partitionKeyRangeId, From 00e32d3f0c975f07a687055abb857b089d077603 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 16 Jul 2024 10:25:22 -0400 Subject: [PATCH 011/145] add a test for FeedRange on Header, Find overlapping ranges using PartitionKey and FeedRange. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 197 +++++++++++++++--- 1 file changed, 168 insertions(+), 29 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 0479bbb353..4c3c62ddee 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -10,8 +10,10 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Linq; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed.Utils; - using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -153,33 +155,34 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - string id = default; - string pk = default; - string description = default; - - foreach (ChangeFeedItem change in docs) - { - if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) - { - id = change.Current.id.ToString(); - pk = change.Current.pk.ToString(); - description = change.Current.description.ToString(); - } - else - { - id = change.Previous.id.ToString(); - pk = change.Previous.pk.ToString(); - description = change.Previous.description.ToString(); - } - - ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousLsn; - DateTime m = change.Metadata.ConflictResolutionTimestamp; - long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; - } - + { + // NOTE(philipthomas-MSFT): Not sure if I need these below. + //string id = default; + //string pk = default; + //string description = default; + + //foreach (ChangeFeedItem change in docs) + //{ + //if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) + //{ + // id = change.Current.id.ToString(); + // pk = change.Current.pk.ToString(); + // description = change.Current.description.ToString(); + //} + //else + //{ + // id = change.Previous.id.ToString(); + // pk = change.Previous.pk.ToString(); + // description = change.Previous.description.ToString(); + //} + + //ChangeFeedOperationType operationType = change.Metadata.OperationType; + //long previousLsn = change.Metadata.PreviousLsn; + //DateTime m = change.Metadata.ConflictResolutionTimestamp; + //long lsn = change.Metadata.Lsn; + //bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + //} + Assert.IsNotNull(context.LeaseToken); Assert.IsNotNull(context.Diagnostics); Assert.IsNotNull(context.Headers); @@ -255,6 +258,142 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.Fail(exception.ToString()); } } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created, then updated, there should be 2 changes that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. The context header also now" + + "has the FeedRange included.")] + public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + Exception exception = default; + + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + // NOTE(philipthomas-MSFT): Not sure if I need these below. + //string id = default; + //string pk = default; + //string description = default; + + foreach (ChangeFeedItem change in docs) + { + FeedRange feedRange = context.Headers.FeedRangeDetails.FeedRange; // FeedRange, Critical + string containerRId = context.Headers.FeedRangeDetails.CollectionRid; // ContainerResourceId, Ancillary + string lsn = context.Headers.ContinuationToken; // LSN, Critical + + Assert.IsNotNull(feedRange); + Logger.LogLine($"{nameof(feedRange)} -> {feedRange.ToJsonString()}"); + + Assert.IsNotNull(containerRId); + Logger.LogLine($"{nameof(containerRId)} -> {containerRId}"); + + Assert.IsNotNull(lsn); + Logger.LogLine($"{nameof(lsn)} -> {lsn}"); + + Documents.Routing.Range range = new Documents.Routing.Range("", "05C1DFFFFFFFFC", true, false); + FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); + string representation = feedRangeEpk.ToJsonString(); + + List ranges = new List() + { + FeedRange.FromJsonString(representation), + }; + + IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( + feedRange: feedRange, + feedRanges: ranges); + + Assert.IsNotNull(overlappingRangesFromFeedRange); + Logger.LogLine($"{nameof(overlappingRangesFromFeedRange)} -> {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); + + string pk = change.Current.pk.ToString(); + PartitionKey partitionKey = new (pk); + + IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync( + partitionKey: partitionKey, + feedRanges: ranges); + + Assert.IsNotNull(overlappingRangesFromPartitionKey); + Logger.LogLine($"{nameof(overlappingRangesFromPartitionKey)} -> {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}"); + + // NOTE(philipthomas-MSFT): Not sure if I need these below. + //id = change.Current.id.ToString(); + //pk = change.Current.pk.ToString(); + //description = change.Current.description.ToString(); + + //ChangeFeedOperationType operationType = change.Metadata.OperationType; + //long previousLsn = change.Metadata.PreviousLsn; + //DateTime m = change.Metadata.ConflictResolutionTimestamp; + //long lsn = change.Metadata.Lsn; + //bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + } + + Assert.IsNotNull(context.LeaseToken); + Assert.IsNotNull(context.Diagnostics); + Assert.IsNotNull(context.Headers); + Assert.IsNotNull(context.Headers.Session); + Assert.IsTrue(context.Headers.RequestCharge > 0); + Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); + Assert.AreEqual(expected: 2, actual: docs.Count); + + ChangeFeedItem createChange = docs.ElementAt(0); + Assert.IsNotNull(createChange.Current); + Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); + Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); + Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); + Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); + Assert.IsNull(createChange.Previous); + + ChangeFeedItem replaceChange = docs.ElementAt(1); + Assert.IsNotNull(replaceChange.Current); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); + Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); + Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); + Assert.IsNull(replaceChange.Previous); + + Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); + + return; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + // Start the processor, insert 1 document to generate a checkpoint, and modify it. + // 1 second delay between operations to get different timestamps. + + await processor.StartAsync(); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + await processor.StopAsync(); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } /// /// This is based on an issue located at . From 8fc860806a7479427c8b34ace4f0d9ba22f7f559 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 16 Jul 2024 10:55:01 -0400 Subject: [PATCH 012/145] removing the type FeedRangeDetails, collectionRId was not needed. --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 9 ++-- .../src/ChangeFeed/FeedRangeDetail.cs | 46 ----------------- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 4 +- ...orBuilderWithAllVersionsAndDeletesTests.cs | 49 +++++-------------- 4 files changed, 18 insertions(+), 90 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index 2bad4f4ffc..ffa57bc512 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -143,13 +143,12 @@ public override async Task ReadNextAsync(ITrace trace, Cancella responseMessage.Headers.ContinuationToken = etag; this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)this.changeFeedStartFrom.FeedRange); - // Set the FeedRangeMinMax response header. + // Set the FeedRange response header. + // NOTE(philipthomas-MSFT): Is this null-check necessary? Under what conditions will feedRangeEpk is null? if (this.feedRangeEpk != null) - { - responseMessage.Headers.FeedRangeDetails = FeedRangeDetail.Create( - feedRange: FeedRangeEpk.FromJsonString(this.feedRangeEpk.ToJsonString()), - collectionRid: responseMessage.RequestMessage.DocumentServiceRequest.ResourceId); + { + responseMessage.Headers.FeedRange = new FeedRangeEpk(this.feedRangeEpk.Range); } return responseMessage; diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs deleted file mode 100644 index 297eae919d..0000000000 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/FeedRangeDetail.cs +++ /dev/null @@ -1,46 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.ChangeFeed -{ - /// - /// The feed range details. - /// - internal class FeedRangeDetail - { - /// - /// Gets the min inclusive. - /// - public FeedRange FeedRange { get; private set; } - - /// - /// Gets the collection resource id. - /// - public string CollectionRid { get; private set; } - - /// - /// Creates a new feed range detail. - /// - /// - /// - /// A immutable feed range detail. - public static FeedRangeDetail Create(FeedRange feedRange, string collectionRid) - { - return new FeedRangeDetail( - feedRange: feedRange, - collectionRid: collectionRid); - } - - /// - /// The construtor for the feed range detail. - /// - /// The minInclusive for the feed range. - /// The collection resource id for the feed range. - private FeedRangeDetail(FeedRange feedRange, string collectionRid) - { - this.FeedRange = feedRange; - this.CollectionRid = collectionRid; - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index 9784180133..2e44e3092f 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -459,9 +459,9 @@ internal static SubStatusCodes GetSubStatusCodes(string value) } /// - /// Gets the feed range details. + /// Gets the feed range. /// - internal virtual ChangeFeed.FeedRangeDetail FeedRangeDetails + internal virtual FeedRange FeedRange { get; set; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 4c3c62ddee..e7172791b6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -267,68 +267,43 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync() { ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + ManualResetEvent allDocsProcessed = new (false); Exception exception = default; ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - // NOTE(philipthomas-MSFT): Not sure if I need these below. - //string id = default; - //string pk = default; - //string description = default; - + { foreach (ChangeFeedItem change in docs) { - FeedRange feedRange = context.Headers.FeedRangeDetails.FeedRange; // FeedRange, Critical - string containerRId = context.Headers.FeedRangeDetails.CollectionRid; // ContainerResourceId, Ancillary - string lsn = context.Headers.ContinuationToken; // LSN, Critical + FeedRange feedRange = context.Headers.FeedRange; // FeedRange + string lsn = context.Headers.ContinuationToken; // LSN Assert.IsNotNull(feedRange); Logger.LogLine($"{nameof(feedRange)} -> {feedRange.ToJsonString()}"); - Assert.IsNotNull(containerRId); - Logger.LogLine($"{nameof(containerRId)} -> {containerRId}"); - Assert.IsNotNull(lsn); Logger.LogLine($"{nameof(lsn)} -> {lsn}"); - Documents.Routing.Range range = new Documents.Routing.Range("", "05C1DFFFFFFFFC", true, false); - FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); - string representation = feedRangeEpk.ToJsonString(); - - List ranges = new List() + List ranges = new () { - FeedRange.FromJsonString(representation), + FeedRange.FromJsonString("{\"Range\":{\"min\":\"\",\"max\":\"05C1DFFFFFFFFC\"}}"), + FeedRange.FromJsonString("{\"Range\":{\"min\":\"08888888888888\",\"max\":\"09999999999999\"}}"), // should be out of range }; + // NOTE(philipthomas-MSFT): FindOverlappingRanges, uses FeedRange. IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( - feedRange: feedRange, + feedRange: feedRange, feedRanges: ranges); Assert.IsNotNull(overlappingRangesFromFeedRange); Logger.LogLine($"{nameof(overlappingRangesFromFeedRange)} -> {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); - string pk = change.Current.pk.ToString(); - PartitionKey partitionKey = new (pk); - - IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync( - partitionKey: partitionKey, - feedRanges: ranges); + // NOTE(philipthomas-MSFT): FindOverlappingRangesAsync, uses PartitionKey. + PartitionKey partitionKey = new (change.Current.pk.ToString()); + IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync(partitionKey: partitionKey, feedRanges: ranges, cancellationToken: token); Assert.IsNotNull(overlappingRangesFromPartitionKey); Logger.LogLine($"{nameof(overlappingRangesFromPartitionKey)} -> {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}"); - - // NOTE(philipthomas-MSFT): Not sure if I need these below. - //id = change.Current.id.ToString(); - //pk = change.Current.pk.ToString(); - //description = change.Current.description.ToString(); - - //ChangeFeedOperationType operationType = change.Metadata.OperationType; - //long previousLsn = change.Metadata.PreviousLsn; - //DateTime m = change.Metadata.ConflictResolutionTimestamp; - //long lsn = change.Metadata.Lsn; - //bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; } Assert.IsNotNull(context.LeaseToken); From d62f05f08ec394fef4d09224c6e917010aca6fd4 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 16 Jul 2024 12:08:16 -0400 Subject: [PATCH 013/145] mark as Preview --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 16 +++---- .../ChangeFeedProcessorContext.cs | 9 +++- .../ChangeFeedObserverContextCore.cs | 7 +++ .../ChangeFeedProcessorContextCore.cs | 9 +++- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 16 ++++--- ...orBuilderWithAllVersionsAndDeletesTests.cs | 44 +++++++++++++++++-- 6 files changed, 77 insertions(+), 24 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index ffa57bc512..c1664fc95d 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -142,17 +142,11 @@ public override async Task ReadNextAsync(ITrace trace, Cancella this.hasMoreResultsInternal = responseMessage.IsSuccessStatusCode; responseMessage.Headers.ContinuationToken = etag; this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)this.changeFeedStartFrom.FeedRange); - - // Set the FeedRange response header. - // NOTE(philipthomas-MSFT): Is this null-check necessary? Under what conditions will feedRangeEpk is null? - - if (this.feedRangeEpk != null) - { - responseMessage.Headers.FeedRange = new FeedRangeEpk(this.feedRangeEpk.Range); - } - + + // Set the FeedRangeEpk response header. + responseMessage.Headers.FeedRangeEpk = this.feedRangeEpk; + return responseMessage; - } - + } } } diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs index 4417b06861..5fc77cd8d1 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs @@ -22,6 +22,13 @@ public abstract class ChangeFeedProcessorContext /// /// Gets the headers related to the service response that provided the changes. /// - public abstract Headers Headers { get; } + public abstract Headers Headers { get; } + +#if PREVIEW + public +#else + internal +#endif + abstract FeedRange FeedRange { get; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs index 4615df9ecb..08e211e940 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs @@ -36,6 +36,13 @@ internal ChangeFeedObserverContextCore( public CosmosDiagnostics Diagnostics => this.responseMessage.Diagnostics; public Headers Headers => this.responseMessage.Headers; + +#if PREVIEW + public +#else + internal +#endif + FeedRange FeedRange => new FeedRangeEpk(this.responseMessage.Headers.FeedRangeEpk.Range); public async Task CheckpointAsync() { diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs index cb4ceb9d2e..00f830ce79 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs @@ -20,6 +20,13 @@ public ChangeFeedProcessorContextCore(ChangeFeedObserverContextCore changeFeedOb public override CosmosDiagnostics Diagnostics => this.changeFeedObserverContextCore.Diagnostics; - public override Headers Headers => this.changeFeedObserverContextCore.Headers; + public override Headers Headers => this.changeFeedObserverContextCore.Headers; + +#if PREVIEW + public +#else + internal +#endif + override FeedRange FeedRange => this.changeFeedObserverContextCore.FeedRange; } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index 2e44e3092f..63823569dd 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -456,15 +456,17 @@ internal static SubStatusCodes GetSubStatusCodes(string value) } return null; - } - - /// - /// Gets the feed range. - /// - internal virtual FeedRange FeedRange + } + +#if PREVIEW + public +#else + internal +#endif + virtual FeedRangeEpk FeedRangeEpk { get; set; - } + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index e7172791b6..ff2f0fa494 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -275,11 +275,11 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( { foreach (ChangeFeedItem change in docs) { - FeedRange feedRange = context.Headers.FeedRange; // FeedRange + FeedRange feedRangeFromContextRoot = context.FeedRange; // FeedRange string lsn = context.Headers.ContinuationToken; // LSN - Assert.IsNotNull(feedRange); - Logger.LogLine($"{nameof(feedRange)} -> {feedRange.ToJsonString()}"); + Assert.IsNotNull(feedRangeFromContextRoot); + Logger.LogLine($"{nameof(feedRangeFromContextRoot)} -> {feedRangeFromContextRoot.ToJsonString()}"); Assert.IsNotNull(lsn); Logger.LogLine($"{nameof(lsn)} -> {lsn}"); @@ -292,7 +292,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( // NOTE(philipthomas-MSFT): FindOverlappingRanges, uses FeedRange. IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( - feedRange: feedRange, + feedRange: feedRangeFromContextRoot, feedRanges: ranges); Assert.IsNotNull(overlappingRangesFromFeedRange); @@ -369,6 +369,42 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( Assert.Fail(exception.ToString()); } } + + ///// + ///// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. + ///// + ///// Critical for invoking GetEPKRangeForPrefixPartitionKey. + ///// Critical for getting range of PartitionKey. + ///// Critical for determining if the current changed lsn has been processed. + ///// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] + ///// Ancillary cancellationToken for downstream async calls. + //public async static Task HasChangeBeenLsnProcessedAsync( + // ContainerInternal monitoredContainer, + // Documents.PartitionKey partitionKey, + // long lsnOfChange, + // IReadOnlyList<(Range range, long lsn)> bookmarks, + // CancellationToken cancellationToken) + //{ + // PartitionKeyDefinition partitionKeyDefinition = await monitoredContainer.GetPartitionKeyDefinitionAsync(cancellationToken); + // Range rangeFromPartitionKey = partitionKey.InternalKey.GetEPKRangeForPrefixPartitionKey(partitionKeyDefinition); + // long highestOverlappingLsn = 0; + + // foreach ((Range range, long lsn) in bookmarks) + // { + // if (PartitionKeyRangeCache.IsStringBetween( + // strToCheck: rangeFromPartitionKey.Max, // NOTE(philipthomas-MSFT): should I be comparing Min, or Max, or Both? + // str1: range.Min, + // str2: range.Max)) + // { + // if (lsn > highestOverlappingLsn) + // { + // highestOverlappingLsn = lsn; + // } + // } + // } + + // return lsnOfChange <= highestOverlappingLsn; + //} /// /// This is based on an issue located at . From 76ee8f056bbe40bc5aad11d49e1f3d54d94052c8 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Wed, 17 Jul 2024 12:29:28 -0400 Subject: [PATCH 014/145] FindOverlappingRanges test. Just FeedRange. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 100 +------------ .../FindOverlappingRangesTests.cs | 140 ++++++++++++++++++ 2 files changed, 144 insertions(+), 96 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index ff2f0fa494..add60e3894 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -9,11 +9,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Diagnostics; using System.Linq; using System.Threading; - using System.Threading.Tasks; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; - using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Services.Management.Tests; - using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -156,33 +154,6 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - // NOTE(philipthomas-MSFT): Not sure if I need these below. - //string id = default; - //string pk = default; - //string description = default; - - //foreach (ChangeFeedItem change in docs) - //{ - //if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) - //{ - // id = change.Current.id.ToString(); - // pk = change.Current.pk.ToString(); - // description = change.Current.description.ToString(); - //} - //else - //{ - // id = change.Previous.id.ToString(); - // pk = change.Previous.pk.ToString(); - // description = change.Previous.description.ToString(); - //} - - //ChangeFeedOperationType operationType = change.Metadata.OperationType; - //long previousLsn = change.Metadata.PreviousLsn; - //DateTime m = change.Metadata.ConflictResolutionTimestamp; - //long lsn = change.Metadata.Lsn; - //bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; - //} - Assert.IsNotNull(context.LeaseToken); Assert.IsNotNull(context.Diagnostics); Assert.IsNotNull(context.Headers); @@ -271,39 +242,12 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( Exception exception = default; ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { foreach (ChangeFeedItem change in docs) { - FeedRange feedRangeFromContextRoot = context.FeedRange; // FeedRange + FeedRange feedRange = context.FeedRange; // FeedRange string lsn = context.Headers.ContinuationToken; // LSN - - Assert.IsNotNull(feedRangeFromContextRoot); - Logger.LogLine($"{nameof(feedRangeFromContextRoot)} -> {feedRangeFromContextRoot.ToJsonString()}"); - - Assert.IsNotNull(lsn); - Logger.LogLine($"{nameof(lsn)} -> {lsn}"); - - List ranges = new () - { - FeedRange.FromJsonString("{\"Range\":{\"min\":\"\",\"max\":\"05C1DFFFFFFFFC\"}}"), - FeedRange.FromJsonString("{\"Range\":{\"min\":\"08888888888888\",\"max\":\"09999999999999\"}}"), // should be out of range - }; - - // NOTE(philipthomas-MSFT): FindOverlappingRanges, uses FeedRange. - IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( - feedRange: feedRangeFromContextRoot, - feedRanges: ranges); - - Assert.IsNotNull(overlappingRangesFromFeedRange); - Logger.LogLine($"{nameof(overlappingRangesFromFeedRange)} -> {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); - - // NOTE(philipthomas-MSFT): FindOverlappingRangesAsync, uses PartitionKey. - PartitionKey partitionKey = new (change.Current.pk.ToString()); - IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync(partitionKey: partitionKey, feedRanges: ranges, cancellationToken: token); - - Assert.IsNotNull(overlappingRangesFromPartitionKey); - Logger.LogLine($"{nameof(overlappingRangesFromPartitionKey)} -> {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}"); } Assert.IsNotNull(context.LeaseToken); @@ -336,7 +280,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - return; + return Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) .WithLeaseContainer(this.LeaseContainer) @@ -370,42 +314,6 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( } } - ///// - ///// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. - ///// - ///// Critical for invoking GetEPKRangeForPrefixPartitionKey. - ///// Critical for getting range of PartitionKey. - ///// Critical for determining if the current changed lsn has been processed. - ///// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] - ///// Ancillary cancellationToken for downstream async calls. - //public async static Task HasChangeBeenLsnProcessedAsync( - // ContainerInternal monitoredContainer, - // Documents.PartitionKey partitionKey, - // long lsnOfChange, - // IReadOnlyList<(Range range, long lsn)> bookmarks, - // CancellationToken cancellationToken) - //{ - // PartitionKeyDefinition partitionKeyDefinition = await monitoredContainer.GetPartitionKeyDefinitionAsync(cancellationToken); - // Range rangeFromPartitionKey = partitionKey.InternalKey.GetEPKRangeForPrefixPartitionKey(partitionKeyDefinition); - // long highestOverlappingLsn = 0; - - // foreach ((Range range, long lsn) in bookmarks) - // { - // if (PartitionKeyRangeCache.IsStringBetween( - // strToCheck: rangeFromPartitionKey.Max, // NOTE(philipthomas-MSFT): should I be comparing Min, or Max, or Both? - // str1: range.Min, - // str2: range.Max)) - // { - // if (lsn > highestOverlappingLsn) - // { - // highestOverlappingLsn = lsn; - // } - // } - // } - - // return lsnOfChange <= highestOverlappingLsn; - //} - /// /// This is based on an issue located at . /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs new file mode 100644 index 0000000000..18fe65b1c7 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs @@ -0,0 +1,140 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + + [TestClass] + public class FindOverlappingRangesTests + { + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("When the feed range has x overlaps for y feed ranges.")] + [DataRow(1, 0)] + [DataRow(2, 0)] + [DataRow(2, 1)] + [DataRow(3, 0)] + [DataRow(3, 1)] + [DataRow(3, 2)] + [DataRow(10, 0)] + [DataRow(10, 1)] + [DataRow(10, 2)] + [DataRow(10, 3)] + [DataRow(10, 4)] + [DataRow(10, 5)] + [DataRow(10, 6)] + [DataRow(10, 7)] + [DataRow(10, 8)] + [DataRow(10, 9)] + public void GivenXFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnAllOverlappingRangesTest( + int numberOfRanges, + int expectedOverlap) + { + ContainerInternal container = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient() + .GetContainer( + databaseId: "TestDb", + containerId: "Test"); + + IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( + minHexValue: "", + maxHexValue: "FFFFFFFFFFFFFFF", + numberOfRanges: numberOfRanges); + + Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( + overlap: expectedOverlap, + feedRanges: feedRanges.ToList()); + + Logger.LogLine($"{feedRange} -> {feedRange.ToJsonString()}"); + + IReadOnlyList overlappingRanges = container.FindOverlappingRanges( + feedRange: feedRange, + feedRanges: feedRanges.ToList()); + + Assert.IsNotNull(overlappingRanges); + Logger.LogLine($"{nameof(overlappingRanges)} -> {JsonConvert.SerializeObject(overlappingRanges)}"); + + int actualOverlap = overlappingRanges.Count; + + Assert.AreEqual( + expected: expectedOverlap + 1, + actual: actualOverlap, + message: $"The given feedRange should have {expectedOverlap + 1} overlaps for {numberOfRanges} feedRanges."); + + Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0))); + Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); + } + + private static Cosmos.FeedRange CreateFeedRangeThatOverlap(int overlap, IReadOnlyList feedRanges) + { + string min = ((FeedRangeEpk)feedRanges[0]).Range.Min; + string max = ((FeedRangeEpk)feedRanges[0 + overlap]).Range.Max; + + Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRange( + min: min, + max: max); + + return feedRange; + } + + private static Cosmos.FeedRange CreateFeedRange(string min, string max) + { + if (min == "0") + { + min = ""; + } + + Documents.Routing.Range range = new ( + min: min, + max: max, + isMinInclusive: true, + isMaxInclusive: false); + + FeedRangeEpk feedRangeEpk = new (range); + + return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); + } + + private static IEnumerable CreateFeedRanges( + string minHexValue, + string maxHexValue, + int numberOfRanges = 10) + { + if (minHexValue == string.Empty) + { + minHexValue = "0"; + } + + // Convert hex strings to ulong + ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); + ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); + + ulong range = maxValue - minValue + 1; // Include the upper boundary + ulong stepSize = range / (ulong)numberOfRanges; + + // Generate the sub-ranges + List<(string, string)> subRanges = new (); + + for (int i = 0; i < numberOfRanges; i++) + { + ulong splitMinValue = minValue + (stepSize * (ulong)i); + ulong splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; + subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); + } + + foreach ((string min, string max) in subRanges) + { + Logger.LogLine($"{min} - {max}"); + + yield return FindOverlappingRangesTests.CreateFeedRange( + min: min, + max: max); + } + } + } +} From 5b2cd52e8b357bda9ecb58b7f6ae9dc7815e5b96 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Wed, 17 Jul 2024 13:24:32 -0400 Subject: [PATCH 015/145] The start, not finished, test for container.FindOverlappingRangesAsync --- .../FindOverlappingRangesTests.cs | 77 ++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs index 18fe65b1c7..ac2fde2b03 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs @@ -7,7 +7,11 @@ namespace Microsoft.Azure.Cosmos.Tests using System; using System.Collections.Generic; using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Routing; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json; [TestClass] @@ -15,7 +19,7 @@ public class FindOverlappingRangesTests { [TestMethod] [Owner("philipthomas-MSFT")] - [Description("When the feed range has x overlaps for y feed ranges.")] + [Description("When a given feed range has x overlaps for y feed ranges.")] [DataRow(1, 0)] [DataRow(2, 0)] [DataRow(2, 1)] @@ -32,14 +36,14 @@ public class FindOverlappingRangesTests [DataRow(10, 7)] [DataRow(10, 8)] [DataRow(10, 9)] - public void GivenXFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnAllOverlappingRangesTest( + public void GivenAFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTest( int numberOfRanges, int expectedOverlap) { ContainerInternal container = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient() .GetContainer( - databaseId: "TestDb", - containerId: "Test"); + databaseId: Guid.NewGuid().ToString(), + containerId: Guid.NewGuid().ToString()); IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( minHexValue: "", @@ -70,6 +74,71 @@ public void GivenXFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnAllOv Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); } + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("When a given partition key has x overlaps for y feed ranges.")] + [DataRow(1, 0)] + [DataRow(2, 0)] + [DataRow(2, 1)] + [DataRow(3, 0)] + [DataRow(3, 1)] + [DataRow(3, 2)] + [DataRow(10, 0)] + [DataRow(10, 1)] + [DataRow(10, 2)] + [DataRow(10, 3)] + [DataRow(10, 4)] + [DataRow(10, 5)] + [DataRow(10, 6)] + [DataRow(10, 7)] + [DataRow(10, 8)] + [DataRow(10, 9)] + public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTestAsync( + int numberOfRanges, + int expectedOverlap) + { + ContainerInternal container = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient() + .GetContainer( + databaseId: Guid.NewGuid().ToString(), + containerId: Guid.NewGuid().ToString()); + + // NOTE(philipthomas-MSFT): Mock these later and finish this test. + //PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + //string effectivePartitionKeyString = partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition); + //CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken); + //PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString); + + IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( + minHexValue: "", + maxHexValue: "FFFFFFFFFFFFFFF", + numberOfRanges: numberOfRanges); + + Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( + overlap: expectedOverlap, + feedRanges: feedRanges.ToList()); + + Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKeyBuilder().Build(); + + Logger.LogLine($"{feedRange} -> {feedRange.ToJsonString()}"); + + IReadOnlyList overlappingRanges = await container.FindOverlappingRangesAsync( + partitionKey: partitionKey, + feedRanges: feedRanges.ToList()); + + Assert.IsNotNull(overlappingRanges); + Logger.LogLine($"{nameof(overlappingRanges)} -> {JsonConvert.SerializeObject(overlappingRanges)}"); + + int actualOverlap = overlappingRanges.Count; + + Assert.AreEqual( + expected: expectedOverlap + 1, + actual: actualOverlap, + message: $"The given feedRange should have {expectedOverlap + 1} overlaps for {numberOfRanges} feedRanges."); + + Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0))); + Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); + } + private static Cosmos.FeedRange CreateFeedRangeThatOverlap(int overlap, IReadOnlyList feedRanges) { string min = ((FeedRangeEpk)feedRanges[0]).Range.Min; From 18ce956f8ae0f2c571ff1ea6ff50a1a4875f3083 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Thu, 18 Jul 2024 11:17:45 -0400 Subject: [PATCH 016/145] partition key tests --- .../FindOverlappingRangesTests.cs | 151 ++++++++++++++---- .../Utils/MockCosmosUtil.cs | 5 +- .../Utils/MockDocumentClient.cs | 10 +- 3 files changed, 129 insertions(+), 37 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs index ac2fde2b03..d82ebc1ef2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs @@ -6,10 +6,14 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Telemetry; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; @@ -47,7 +51,7 @@ public void GivenAFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOver IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( minHexValue: "", - maxHexValue: "FFFFFFFFFFFFFFF", + maxHexValue: "FF", numberOfRanges: numberOfRanges); Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( @@ -79,28 +83,89 @@ public void GivenAFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOver [Description("When a given partition key has x overlaps for y feed ranges.")] [DataRow(1, 0)] [DataRow(2, 0)] - [DataRow(2, 1)] - [DataRow(3, 0)] - [DataRow(3, 1)] - [DataRow(3, 2)] - [DataRow(10, 0)] - [DataRow(10, 1)] - [DataRow(10, 2)] - [DataRow(10, 3)] - [DataRow(10, 4)] - [DataRow(10, 5)] - [DataRow(10, 6)] - [DataRow(10, 7)] - [DataRow(10, 8)] - [DataRow(10, 9)] + //[DataRow(2, 1)] public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTestAsync( int numberOfRanges, int expectedOverlap) { - ContainerInternal container = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient() - .GetContainer( - databaseId: Guid.NewGuid().ToString(), - containerId: Guid.NewGuid().ToString()); + ITrace trace = Microsoft.Azure.Cosmos.Tracing.Trace.GetRootTrace("TestTrace"); + + IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( + minHexValue: "", + maxHexValue: "FF", + numberOfRanges: numberOfRanges); + + List> partitionKeyRanges = new List>(feedRanges.Count()); + + for (int counter = 0; counter < feedRanges.Count(); counter++) + { + Documents.Routing.Range range = ((FeedRangeEpk)feedRanges.ElementAt(counter)).Range; + + partitionKeyRanges.Add( + new Tuple( + new PartitionKeyRange + { + Id = counter.ToString(), + MinInclusive = range.Min, + MaxExclusive = range.Max + }, + (ServiceIdentity)null)); + } + + CollectionRoutingMap collectionRoutingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( + ranges: partitionKeyRanges, + collectionUniqueId: string.Empty); + + ContainerInternal originalContainer = (ContainerInternal)MockCosmosUtil + .CreateMockCosmosClient(collectionRoutingMap: collectionRoutingMap) + .GetContainer("TestDb", "TestContainer"); + + Mock mockedContext = this.MockClientContext(); + //mockedContext.Setup(c => c.ClientOptions).Returns(new CosmosClientOptions()); + //mockedContext.Setup(c => c.SerializerCore).Returns(MockCosmosUtil.Serializer); + + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition() { Paths = new Collection() { "/pk" } }; + + string link = "dbs/DvZRAA==/colls/DvZRAOvLgDM=/"; + const string collectionRid = "DvZRAOvLgDM="; + ContainerProperties containerProperties = ContainerProperties.CreateWithResourceId(collectionRid); + containerProperties.Id = "TestContainer"; + containerProperties.PartitionKey = partitionKeyDefinition; + + Mock mockDocumentClient = new Mock(); + mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); + + using GlobalEndpointManager endpointManager = new(mockDocumentClient.Object, new ConnectionPolicy()); + + Mock mockContainer = new Mock(); + mockContainer.Setup(x => x.LinkUri).Returns(link); + mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(containerProperties)); + + + Mock collectionCache = new Mock(MockBehavior.Strict); + collectionCache.Setup(c => c.ResolveCollectionAsync(It.IsAny(), default, trace)) + .ReturnsAsync(containerProperties); + + mockContainer + .Setup(containerInternal => containerInternal.GetPartitionKeyDefinitionAsync(It.IsAny())) + .Returns(Task.FromResult(partitionKeyDefinition)); + + Mock partitionKeyRangeCache = new Mock( + MockBehavior.Strict, + new Mock().Object, + new Mock().Object, + collectionCache.Object, + endpointManager); + partitionKeyRangeCache.Setup(c => c.TryLookupAsync("test", null, null, NoOpTrace.Singleton)) + .ReturnsAsync(collectionRoutingMap); + + Logger.LogLine($"{nameof(collectionRoutingMap)} -> {JsonConvert.SerializeObject(collectionRoutingMap)}"); + + mockContainer + .Setup(containerInternal => containerInternal.GetRoutingMapAsync(It.IsAny())) + .Returns(Task.FromResult(collectionRoutingMap)); + // NOTE(philipthomas-MSFT): Mock these later and finish this test. //PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); @@ -108,20 +173,18 @@ public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenRe //CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken); //PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString); - IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( - minHexValue: "", - maxHexValue: "FFFFFFFFFFFFFFF", - numberOfRanges: numberOfRanges); Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( overlap: expectedOverlap, feedRanges: feedRanges.ToList()); - - Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKeyBuilder().Build(); + + Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKeyBuilder() + .Add("GA") + .Build(); Logger.LogLine($"{feedRange} -> {feedRange.ToJsonString()}"); - IReadOnlyList overlappingRanges = await container.FindOverlappingRangesAsync( + IReadOnlyList overlappingRanges = await originalContainer.FindOverlappingRangesAsync( partitionKey: partitionKey, feedRanges: feedRanges.ToList()); @@ -139,6 +202,27 @@ public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenRe Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); } + private Mock MockClientContext() + { + Mock mockContext = new Mock(); + mockContext.Setup(x => x.OperationHelperAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>>(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Returns>, Func, TraceComponent, Microsoft.Azure.Cosmos.Tracing.TraceLevel>( + (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, comp, level) => func(NoOpTrace.Singleton)); + + mockContext.Setup(x => x.Client).Returns(MockCosmosUtil.CreateMockCosmosClient()); + + return mockContext; + } + private static Cosmos.FeedRange CreateFeedRangeThatOverlap(int overlap, IReadOnlyList feedRanges) { string min = ((FeedRangeEpk)feedRanges[0]).Range.Min; @@ -188,22 +272,27 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) // Generate the sub-ranges List<(string, string)> subRanges = new (); + ulong splitMaxValue = default; for (int i = 0; i < numberOfRanges; i++) { - ulong splitMinValue = minValue + (stepSize * (ulong)i); - ulong splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; + ulong splitMinValue = splitMaxValue; + splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); } - + + List rs = new List(); + foreach ((string min, string max) in subRanges) { Logger.LogLine($"{min} - {max}"); - yield return FindOverlappingRangesTests.CreateFeedRange( + rs.Add(FindOverlappingRangesTests.CreateFeedRange( min: min, - max: max); + max: max)); } + + return rs; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs index 77ba6981cb..a14e01b9ec 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs @@ -32,7 +32,8 @@ internal class MockCosmosUtil public static CosmosClient CreateMockCosmosClient( Action customizeClientBuilder = null, Cosmos.ConsistencyLevel? accountConsistencyLevel = null, - bool enableTelemetry = false) + bool enableTelemetry = false, + CollectionRoutingMap collectionRoutingMap = null) { ConnectionPolicy policy = new ConnectionPolicy { @@ -42,7 +43,7 @@ public static CosmosClient CreateMockCosmosClient( } }; - DocumentClient documentClient = accountConsistencyLevel.HasValue ? new MockDocumentClient(accountConsistencyLevel.Value, policy) : new MockDocumentClient(policy); + DocumentClient documentClient = accountConsistencyLevel.HasValue ? new MockDocumentClient(accountConsistencyLevel.Value, policy) : new MockDocumentClient(policy, collectionRoutingMap); CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder("http://localhost", MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey); customizeClientBuilder?.Invoke(cosmosClientBuilder); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs index b6498dec6f..4eb9a01dff 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs @@ -27,10 +27,12 @@ internal class MockDocumentClient : DocumentClient, IAuthorizationTokenProvider, Mock collectionCache; Mock partitionKeyRangeCache; private readonly Cosmos.ConsistencyLevel accountConsistencyLevel; - - public MockDocumentClient(ConnectionPolicy connectionPolicy = null) + private readonly CollectionRoutingMap collectionRoutingMap; + + public MockDocumentClient(ConnectionPolicy connectionPolicy = null, CollectionRoutingMap collectionRoutingMap = null) : base(new Uri("http://localhost"), MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey, connectionPolicy) - { + { + this.collectionRoutingMap = collectionRoutingMap; this.Init(); } @@ -242,7 +244,7 @@ private void Init() It.IsAny(), It.IsAny() ) - ).Returns(Task.FromResult(null)); + ).Returns(Task.FromResult(this.collectionRoutingMap)); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), From b1ed9a1e88de5fe54be266277fd9c7e27b17384a Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Thu, 18 Jul 2024 13:43:24 -0400 Subject: [PATCH 017/145] more testing. still not satisfied. will do more until the end of week. --- .../src/Resource/Container/ContainerCore.cs | 6 +- .../FindOverlappingRangesTests.cs | 102 ++++-------------- .../Utils/MockCosmosUtil.cs | 5 +- .../Utils/MockDocumentClient.cs | 10 +- 4 files changed, 30 insertions(+), 93 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 3a1e80916b..a3e3d25ebe 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -742,11 +742,9 @@ public override FeedIterator GetChangeFeedIteratorWithQuery( private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) { PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - string effectivePartitionKeyString = partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition); - CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken); - PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString); + Range range = Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); - return partitionKeyRange.ToRange(); + return range; } private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs index d82ebc1ef2..4daa099167 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs @@ -6,11 +6,8 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Linq; - using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -80,22 +77,23 @@ public void GivenAFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOver [TestMethod] [Owner("philipthomas-MSFT")] - [Description("When a given partition key has x overlaps for y feed ranges.")] - [DataRow(1, 0)] - [DataRow(2, 0)] - //[DataRow(2, 1)] + [Description("Given a partition key and a X number of ranges, When checking to find which range the partition key belongs to, Then the" + + " range is found at id Y.")] + [DataRow(1, 1)] + [DataRow(2, 1)] + [DataRow(10, 1)] public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTestAsync( int numberOfRanges, - int expectedOverlap) + int expectedRangeId) { - ITrace trace = Microsoft.Azure.Cosmos.Tracing.Trace.GetRootTrace("TestTrace"); + ITrace trace = Trace.GetRootTrace("TestTrace"); IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( minHexValue: "", maxHexValue: "FF", numberOfRanges: numberOfRanges); - List> partitionKeyRanges = new List>(feedRanges.Count()); + List> partitionKeyRanges = new (feedRanges.Count()); for (int counter = 0; counter < feedRanges.Count(); counter++) { @@ -112,79 +110,24 @@ public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenRe (ServiceIdentity)null)); } - CollectionRoutingMap collectionRoutingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( - ranges: partitionKeyRanges, - collectionUniqueId: string.Empty); - - ContainerInternal originalContainer = (ContainerInternal)MockCosmosUtil - .CreateMockCosmosClient(collectionRoutingMap: collectionRoutingMap) - .GetContainer("TestDb", "TestContainer"); - - Mock mockedContext = this.MockClientContext(); - //mockedContext.Setup(c => c.ClientOptions).Returns(new CosmosClientOptions()); - //mockedContext.Setup(c => c.SerializerCore).Returns(MockCosmosUtil.Serializer); + string containerId = Guid.NewGuid().ToString(); + ContainerInternal container = (ContainerInternal)MockCosmosUtil + .CreateMockCosmosClient() + .GetContainer( + databaseId: Guid.NewGuid().ToString(), + containerId: containerId); - PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition() { Paths = new Collection() { "/pk" } }; + PartitionKeyDefinition partitionKeyDefinition = new(); + partitionKeyDefinition.Paths.Add("/pk"); + partitionKeyDefinition.Version = PartitionKeyDefinitionVersion.V1; + Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKey("test"); - string link = "dbs/DvZRAA==/colls/DvZRAOvLgDM=/"; const string collectionRid = "DvZRAOvLgDM="; ContainerProperties containerProperties = ContainerProperties.CreateWithResourceId(collectionRid); - containerProperties.Id = "TestContainer"; + containerProperties.Id = containerId; containerProperties.PartitionKey = partitionKeyDefinition; - Mock mockDocumentClient = new Mock(); - mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - - using GlobalEndpointManager endpointManager = new(mockDocumentClient.Object, new ConnectionPolicy()); - - Mock mockContainer = new Mock(); - mockContainer.Setup(x => x.LinkUri).Returns(link); - mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(Task.FromResult(containerProperties)); - - - Mock collectionCache = new Mock(MockBehavior.Strict); - collectionCache.Setup(c => c.ResolveCollectionAsync(It.IsAny(), default, trace)) - .ReturnsAsync(containerProperties); - - mockContainer - .Setup(containerInternal => containerInternal.GetPartitionKeyDefinitionAsync(It.IsAny())) - .Returns(Task.FromResult(partitionKeyDefinition)); - - Mock partitionKeyRangeCache = new Mock( - MockBehavior.Strict, - new Mock().Object, - new Mock().Object, - collectionCache.Object, - endpointManager); - partitionKeyRangeCache.Setup(c => c.TryLookupAsync("test", null, null, NoOpTrace.Singleton)) - .ReturnsAsync(collectionRoutingMap); - - Logger.LogLine($"{nameof(collectionRoutingMap)} -> {JsonConvert.SerializeObject(collectionRoutingMap)}"); - - mockContainer - .Setup(containerInternal => containerInternal.GetRoutingMapAsync(It.IsAny())) - .Returns(Task.FromResult(collectionRoutingMap)); - - - // NOTE(philipthomas-MSFT): Mock these later and finish this test. - //PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - //string effectivePartitionKeyString = partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition); - //CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken); - //PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString); - - - Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( - overlap: expectedOverlap, - feedRanges: feedRanges.ToList()); - - Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKeyBuilder() - .Add("GA") - .Build(); - - Logger.LogLine($"{feedRange} -> {feedRange.ToJsonString()}"); - - IReadOnlyList overlappingRanges = await originalContainer.FindOverlappingRangesAsync( + IReadOnlyList overlappingRanges = await container.FindOverlappingRangesAsync( partitionKey: partitionKey, feedRanges: feedRanges.ToList()); @@ -194,12 +137,11 @@ public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenRe int actualOverlap = overlappingRanges.Count; Assert.AreEqual( - expected: expectedOverlap + 1, + expected: expectedRangeId, actual: actualOverlap, - message: $"The given feedRange should have {expectedOverlap + 1} overlaps for {numberOfRanges} feedRanges."); + message: $"The given partition key should be at range {expectedRangeId}."); Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0))); - Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); } private Mock MockClientContext() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs index a14e01b9ec..77ba6981cb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs @@ -32,8 +32,7 @@ internal class MockCosmosUtil public static CosmosClient CreateMockCosmosClient( Action customizeClientBuilder = null, Cosmos.ConsistencyLevel? accountConsistencyLevel = null, - bool enableTelemetry = false, - CollectionRoutingMap collectionRoutingMap = null) + bool enableTelemetry = false) { ConnectionPolicy policy = new ConnectionPolicy { @@ -43,7 +42,7 @@ public static CosmosClient CreateMockCosmosClient( } }; - DocumentClient documentClient = accountConsistencyLevel.HasValue ? new MockDocumentClient(accountConsistencyLevel.Value, policy) : new MockDocumentClient(policy, collectionRoutingMap); + DocumentClient documentClient = accountConsistencyLevel.HasValue ? new MockDocumentClient(accountConsistencyLevel.Value, policy) : new MockDocumentClient(policy); CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder("http://localhost", MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey); customizeClientBuilder?.Invoke(cosmosClientBuilder); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs index 4eb9a01dff..b6498dec6f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs @@ -27,12 +27,10 @@ internal class MockDocumentClient : DocumentClient, IAuthorizationTokenProvider, Mock collectionCache; Mock partitionKeyRangeCache; private readonly Cosmos.ConsistencyLevel accountConsistencyLevel; - private readonly CollectionRoutingMap collectionRoutingMap; - - public MockDocumentClient(ConnectionPolicy connectionPolicy = null, CollectionRoutingMap collectionRoutingMap = null) + + public MockDocumentClient(ConnectionPolicy connectionPolicy = null) : base(new Uri("http://localhost"), MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey, connectionPolicy) - { - this.collectionRoutingMap = collectionRoutingMap; + { this.Init(); } @@ -244,7 +242,7 @@ private void Init() It.IsAny(), It.IsAny() ) - ).Returns(Task.FromResult(this.collectionRoutingMap)); + ).Returns(Task.FromResult(null)); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), From df0eb3219f0f98d65c25cd2fabb05bb9fd3e4159 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Mon, 22 Jul 2024 10:18:41 -0400 Subject: [PATCH 018/145] more test changes. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 174 ++++++++++++++++-- 1 file changed, 158 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index add60e3894..582d9d1af3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -7,11 +7,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System; using System.Collections.Generic; using System.Diagnostics; - using System.Linq; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -40,7 +41,7 @@ public async Task Cleanup() "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); Exception exception = default; int ttlInSeconds = 5; Stopwatch stopwatch = new(); @@ -147,7 +148,7 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new ManualResetEvent(false); Exception exception = default; @@ -234,20 +235,49 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() [Owner("philipthomas-MSFT")] [Description("Scenario: When a document is created, then updated, there should be 2 changes that will appear for that " + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. The context header also now" + - "has the FeedRange included.")] + "has the FeedRange included. This is simulating a customer scenario using FindOverlappingRanges.")] public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync() { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new (false); Exception exception = default; ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { foreach (ChangeFeedItem change in docs) { FeedRange feedRange = context.FeedRange; // FeedRange - string lsn = context.Headers.ContinuationToken; // LSN + _ = long.TryParse(context.Headers.ContinuationToken.Trim('"'), out long lsnOfChange); // LSN + + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: containerResponse.Resource.ResourceId, + range: FeedRangeEpk.FullRange.Range, + trace: NoOpTrace.Singleton, + forceRefresh: true); + + IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( + minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, + maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, + numberOfRanges: 3); + + List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .CreateBookmarks(bookmarkRanges); + + bool hasChangeBeenLsnProcessed = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .HasChangeBeenLsnProcessed( + monitoredContainer: monitoredContainer, + feedRange: feedRange, + lsnOfChange: lsnOfChange, + bookmarks: bookmarks); + + Logger.LogLine($"{nameof(hasChangeBeenLsnProcessed)} -> {hasChangeBeenLsnProcessed}"); + + if (hasChangeBeenLsnProcessed) + { + // Know what? + } } Assert.IsNotNull(context.LeaseToken); @@ -280,7 +310,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - return Task.CompletedTask; + return; // Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) .WithLeaseContainer(this.LeaseContainer) @@ -314,6 +344,14 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( } } + private static List<(FeedRange range, long lsn)> CreateBookmarks(IEnumerable bookmarkRanges) + { + return bookmarkRanges + .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) + .ToList(); + + } + /// /// This is based on an issue located at . /// @@ -325,7 +363,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( [DataRow(true)] public async Task WhenLatestVersionSwitchToAllVersionsAndDeletesExpectsAexceptionTestAsync(bool withStartFromBeginning) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -357,7 +395,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenLegacyLatestVersionSwitchToAllVersionsAndDeletesExpectsAexceptionTestAsync(bool withStartFromBeginning) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -395,7 +433,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenAllVersionsAndDeletesSwitchToLatestVersionExpectsAexceptionTestAsync(bool withStartFromBeginning) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -424,7 +462,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests "no exception is expected.")] public async Task WhenNoSwitchAllVersionsAndDeletesFDoesNotExpectAexceptionTestAsync() { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new(false); try @@ -458,7 +496,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenNoSwitchLatestVersionDoesNotExpectAexceptionTestAsync(bool withStartFromBeginning) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); try @@ -494,7 +532,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenLegacyNoSwitchLatestVersionDoesNotExpectAnExceptionTestAsync(bool withStartFromBeginning) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -628,7 +666,7 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync } } - private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) + private async Task<(ContainerInternal, ContainerResponse)> CreateMonitoredContainer(ChangeFeedMode changeFeedMode) { string PartitionKey = "/pk"; ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), @@ -644,7 +682,111 @@ private async Task CreateMonitoredContainer(ChangeFeedMode ch throughput: 10000, cancellationToken: this.cancellationToken); - return (ContainerInternal)response; + return ((ContainerInternal)response, response); + } + + private static Cosmos.FeedRange CreateFeedRange(string min, string max) + { + if (min == "0") + { + min = ""; + } + + Documents.Routing.Range range = new( + min: min, + max: max, + isMinInclusive: true, + isMaxInclusive: false); + + FeedRangeEpk feedRangeEpk = new(range); + + return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); + } + + private static IEnumerable CreateFeedRanges( + string minHexValue, + string maxHexValue, + int numberOfRanges = 10) + { + if (minHexValue == string.Empty) + { + minHexValue = "0"; + } + + // Convert hex strings to ulong + ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); + ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); + + ulong range = maxValue - minValue + 1; // Include the upper boundary + ulong stepSize = range / (ulong)numberOfRanges; + + // Generate the sub-ranges + List<(string, string)> subRanges = new(); + ulong splitMaxValue = default; + + for (int i = 0; i < numberOfRanges; i++) + { + ulong splitMinValue = splitMaxValue; + splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; + subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); + } + + List feedRanges = new List(); + + foreach ((string min, string max) in subRanges) + { + feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( + min: min, + max: max)); + } + + return feedRanges; + } + + /// + /// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. + /// + /// Critical for invoking GetEPKRangeForPrefixPartitionKey. + /// Critical for determining if the current changed lsn has been processed. + /// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] + private static bool HasChangeBeenLsnProcessed( + ContainerInternal monitoredContainer, + FeedRange feedRange, + long lsnOfChange, + IReadOnlyList<(FeedRange range, long lsn)> bookmarks) + { + IReadOnlyList overlappingRanges = monitoredContainer.FindOverlappingRanges( + feedRange: feedRange, + feedRanges: bookmarks.Select(bookmark => bookmark.range).ToList()); + + if (overlappingRanges == null) + { + return false; + } + + Logger.LogLine($"{nameof(feedRange)} -> {feedRange.ToJsonString()}"); + Logger.LogLine($"{nameof(lsnOfChange)} -> {lsnOfChange}"); + Logger.LogLine($"{nameof(bookmarks)} -> {JsonConvert.SerializeObject(bookmarks)}"); + + foreach (FeedRange overlappingRange in overlappingRanges) + { + foreach ((FeedRange range, long lsn) in bookmarks.Select(x => x)) + { + if (lsnOfChange >= lsn && overlappingRange.Equals(range)) + { + Logger.LogLine($"The range '{range}' with lsn '{lsn}' has been processed."); + + return true; + } + else + { + Logger.LogLine($"The range '{range}' with lsn '{lsn}' has not been processed."); + } + } + } + + return false; + } } } From 77be0f9e2e44048556015d77e00e1da014de3a29 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Mon, 22 Jul 2024 10:45:37 -0400 Subject: [PATCH 019/145] add partitionkey to test --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 582d9d1af3..09a5bf4264 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -240,7 +240,8 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( { (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new (false); - Exception exception = default; + Exception exception = default; + PartitionKey partitionKey = new PartitionKey("1"); ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => @@ -250,33 +251,23 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( FeedRange feedRange = context.FeedRange; // FeedRange _ = long.TryParse(context.Headers.ContinuationToken.Trim('"'), out long lsnOfChange); // LSN - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: containerResponse.Resource.ResourceId, - range: FeedRangeEpk.FullRange.Range, - trace: NoOpTrace.Singleton, - forceRefresh: true); - - IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( - minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, - maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, - numberOfRanges: 3); + // Bookmarks would otherwise normaly be read from the customer's changed items, but I am generating this for test purposes. + List<(FeedRange range, long lsn)> bookmarks = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .CreateTestBookmarksAsync( + cosmosClient: this.GetClient(), + containerRId: containerResponse.Resource.ResourceId); - List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .CreateBookmarks(bookmarkRanges); - - bool hasChangeBeenLsnProcessed = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .HasChangeBeenLsnProcessed( + // The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. + bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .HasChangeBeedProcessedAsync( + partitionKey: partitionKey, monitoredContainer: monitoredContainer, - feedRange: feedRange, lsnOfChange: lsnOfChange, bookmarks: bookmarks); - Logger.LogLine($"{nameof(hasChangeBeenLsnProcessed)} -> {hasChangeBeenLsnProcessed}"); - - if (hasChangeBeenLsnProcessed) + if (hasChangedBeenProcessed) { - // Know what? + // Now? Up to customer to decide what to do. } } @@ -344,9 +335,23 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( } } - private static List<(FeedRange range, long lsn)> CreateBookmarks(IEnumerable bookmarkRanges) + private static async Task> CreateTestBookmarksAsync( + CosmosClient cosmosClient, + string containerRId) { - return bookmarkRanges + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: containerRId, + range: FeedRangeEpk.FullRange.Range, + trace: NoOpTrace.Singleton, + forceRefresh: true); + + IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( + minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, + maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, + numberOfRanges: 3); + + return bookmarkRanges .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) .ToList(); @@ -749,17 +754,17 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) /// Critical for invoking GetEPKRangeForPrefixPartitionKey. /// Critical for determining if the current changed lsn has been processed. /// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] - private static bool HasChangeBeenLsnProcessed( + private static bool HasChangeBeedProcessed( ContainerInternal monitoredContainer, FeedRange feedRange, long lsnOfChange, IReadOnlyList<(FeedRange range, long lsn)> bookmarks) { - IReadOnlyList overlappingRanges = monitoredContainer.FindOverlappingRanges( + IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( feedRange: feedRange, feedRanges: bookmarks.Select(bookmark => bookmark.range).ToList()); - if (overlappingRanges == null) + if (overlappingRangesFromFeedRange == null) { return false; } @@ -768,19 +773,51 @@ private static bool HasChangeBeenLsnProcessed( Logger.LogLine($"{nameof(lsnOfChange)} -> {lsnOfChange}"); Logger.LogLine($"{nameof(bookmarks)} -> {JsonConvert.SerializeObject(bookmarks)}"); - foreach (FeedRange overlappingRange in overlappingRanges) + foreach (FeedRange overlappingRange in overlappingRangesFromFeedRange) { foreach ((FeedRange range, long lsn) in bookmarks.Select(x => x)) { - if (lsnOfChange >= lsn && overlappingRange.Equals(range)) + if (lsnOfChange <= lsn && overlappingRange.Equals(range)) { Logger.LogLine($"The range '{range}' with lsn '{lsn}' has been processed."); return true; } - else + } + } + + return false; + + } + + private static async Task HasChangeBeedProcessedAsync( + ContainerInternal monitoredContainer, + PartitionKey partitionKey, + long lsnOfChange, + IReadOnlyList<(FeedRange range, long lsn)> bookmarks) + { + IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync( + partitionKey, + bookmarks.Select(bookmark => bookmark.range).ToList()); + + if (overlappingRangesFromPartitionKey == null) + { + return false; + } + + Logger.LogLine($"{nameof(partitionKey)} -> {partitionKey.ToJsonString()}"); + Logger.LogLine($"{nameof(lsnOfChange)} -> {lsnOfChange}"); + Logger.LogLine($"{nameof(bookmarks)} -> {JsonConvert.SerializeObject(bookmarks)}"); + + foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey) + { + foreach ((FeedRange range, long lsn) in bookmarks.Select(x => x)) + { + if (lsnOfChange <= lsn && overlappingRange.Equals(range)) { - Logger.LogLine($"The range '{range}' with lsn '{lsn}' has not been processed."); + Logger.LogLine($"The range '{range}' with lsn '{lsn}' has been processed."); + + return true; } } } From 843be8ffce8ab7bb189e2792b334ec31405f41dd Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 10:11:05 -0400 Subject: [PATCH 020/145] re-run UpdateContracts.ps1 --- .../Contracts/DotNetPreviewSDKAPI.json | 4 ++-- .../Contracts/DotNetSDKAPI.json | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) 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 4a4b7b08dc..9ea60dd372 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 @@ -149,10 +149,10 @@ ], "MethodInfo": "System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor()": { + "Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(), Void .ctor()]" + "MethodInfo": "[Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" } }, "NestedTypes": {} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 89074f0fef..02c91009e6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -1502,6 +1502,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.TransactionalBatch CreateTransactionalBatch(Microsoft.Azure.Cosmos.PartitionKey);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Linq.IOrderedQueryable`1[T] GetItemLinqQueryable[T](Boolean, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions)": { "Type": "Method", "Attributes": [], @@ -1632,6 +1637,11 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ThroughputResponse] ReplaceThroughputAsync(Microsoft.Azure.Cosmos.ThroughputProperties, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] GetFeedRangesAsync(System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], From 5cbb0d61dbfb095060595a75294dd0081042bc8a Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 23 Jul 2024 12:28:26 -0400 Subject: [PATCH 021/145] more tests alterations. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 268 +++++++++--------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index e1878dfd72..7febfe305d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Services.Management.Tests; - using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -233,75 +232,60 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() [TestMethod] [Owner("philipthomas-MSFT")] - [Description("Scenario: When a document is created, then updated, there should be 2 changes that will appear for that " + - "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. The context header also now" + - "has the FeedRange included. This is simulating a customer scenario using FindOverlappingRanges.")] - public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync() + [Description("Scenario: When documents are created, the document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. " + + "The context header also now has the FeedRange included. This is simulating a customer scenario using HasChangeBeenProcessed and FindOverlappingRanges.")] + public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAsync() { (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new (false); Exception exception = default; - PartitionKey partitionKey = new PartitionKey("1"); ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - foreach (ChangeFeedItem change in docs) - { - FeedRange feedRange = context.FeedRange; // FeedRange - _ = long.TryParse(context.Headers.ContinuationToken.Trim('"'), out long lsnOfChange); // LSN - - // Bookmarks would otherwise normaly be read from the customer's changed items, but I am generating this for test purposes. - List<(FeedRange range, long lsn)> bookmarks = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .CreateTestBookmarksAsync( - cosmosClient: this.GetClient(), - containerRId: containerResponse.Resource.ResourceId); - - // The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. + foreach (ChangeFeedItem document in docs) + { + // NOTE(philipthomas-MSFT): Get all that we need, FeedRange, LsnOfChange, PartitionKey, and Bookmarks if exist. + + // 1.) FeedRange + FeedRange feedRange = context.FeedRange; + + // 2.) LsnOfChange + _ = long.TryParse( + s: context.Headers.ContinuationToken.Trim('"'), + result: out long lsnOfChange); + + // 3.) PartitionKey + PartitionKey partitionKey = new(document.Current.pk.ToString()); + + // 4. Bookmarks + List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateBookmarksIfNotExists(document); + + // 5. Log + Logger.LogLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChange}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + + // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .HasChangeBeedProcessedAsync( + .HasChangeBeenProcessedAsync( partitionKey: partitionKey, monitoredContainer: monitoredContainer, lsnOfChange: lsnOfChange, bookmarks: bookmarks); - if (hasChangedBeenProcessed) - { - // Now? Up to customer to decide what to do. + if (!hasChangedBeenProcessed) + { + // 7.a. Update bookmarks if not been processed so that it is picked up next time. + bookmarks.Add((feedRange, lsnOfChange)); + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = document.Current.id.ToString(), pk = document.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey); + await Task.Delay(1000); + } + else + { + // 7.b. Do nothing? } - } - - Assert.IsNotNull(context.LeaseToken); - Assert.IsNotNull(context.Diagnostics); - Assert.IsNotNull(context.Headers); - Assert.IsNotNull(context.Headers.Session); - Assert.IsTrue(context.Headers.RequestCharge > 0); - Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); - Assert.AreEqual(expected: 2, actual: docs.Count); - - ChangeFeedItem createChange = docs.ElementAt(0); - Assert.IsNotNull(createChange.Current); - Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); - Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); - Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); - Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); - Assert.IsNull(createChange.Previous); - - ChangeFeedItem replaceChange = docs.ElementAt(1); - Assert.IsNotNull(replaceChange.Current); - Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); - Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); - Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); - Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); - Assert.IsNull(replaceChange.Previous); + } - Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - - return; // Task.CompletedTask; + return; }) .WithInstanceName(Guid.NewGuid().ToString()) .WithLeaseContainer(this.LeaseContainer) @@ -319,11 +303,17 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( await processor.StartAsync(); await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); - await Task.Delay(1000); + await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "original test" }, partitionKey: new PartitionKey("WA")); + await Task.Delay(1000); + + await monitoredContainer.CreateItemAsync(new { id = "2", pk = "GA", description = "original test" }, partitionKey: new PartitionKey("GA")); + await Task.Delay(1000); - await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); - await Task.Delay(1000); + //await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "WA", description = "test after replace" }, partitionKey: new PartitionKey("WA")); + //await Task.Delay(1000); + + //await monitoredContainer.UpsertItemAsync(new { id = "2", pk = "GA", description = "test after replace" }, partitionKey: new PartitionKey("GA")); + //await Task.Delay(1000); bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); @@ -333,29 +323,47 @@ public async Task WhenADocumentIsCreatedThenUpdatedHeaderHasFeedRangeTestsAsync( { Assert.Fail(exception.ToString()); } - } - - private static async Task> CreateTestBookmarksAsync( - CosmosClient cosmosClient, - string containerRId) - { - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: containerRId, - range: FeedRangeEpk.FullRange.Range, - trace: NoOpTrace.Singleton, - forceRefresh: true); - - IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( - minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, - maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, - numberOfRanges: 3); - - return bookmarkRanges - .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) - .ToList(); - - } + } + + private static List<(FeedRange range, long lsn)> CreateBookmarksIfNotExists(ChangeFeedItem change) + { + List<(FeedRange range, long lsn)> bookmarks = new(); + + if (change.Current.bookmarks != null) + { + foreach (dynamic bookmark in change.Current.bookmarks) + { + JObject asJObject = JObject.Parse(bookmark.ToString()); + + bookmarks.Add((FeedRange.FromJsonString(asJObject["Item1"].ToString()), long.Parse(asJObject["Item2"].ToString()))); + } + + } + + return bookmarks; + } + + //private static async Task> CreateTestBookmarksAsync( + // CosmosClient cosmosClient, + // string containerRId) + //{ + // Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + // IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + // collectionRid: containerRId, + // range: FeedRangeEpk.FullRange.Range, + // trace: NoOpTrace.Singleton, + // forceRefresh: true); + + // IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( + // minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, + // maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, + // numberOfRanges: 3); + + // return bookmarkRanges + // .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) + // .ToList(); + + //} /// /// This is based on an issue located at . @@ -708,45 +716,45 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); } - private static IEnumerable CreateFeedRanges( - string minHexValue, - string maxHexValue, - int numberOfRanges = 10) - { - if (minHexValue == string.Empty) - { - minHexValue = "0"; - } - - // Convert hex strings to ulong - ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); - ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); - - ulong range = maxValue - minValue + 1; // Include the upper boundary - ulong stepSize = range / (ulong)numberOfRanges; - - // Generate the sub-ranges - List<(string, string)> subRanges = new(); - ulong splitMaxValue = default; - - for (int i = 0; i < numberOfRanges; i++) - { - ulong splitMinValue = splitMaxValue; - splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; - subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); - } - - List feedRanges = new List(); - - foreach ((string min, string max) in subRanges) - { - feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( - min: min, - max: max)); - } - - return feedRanges; - } + //private static IEnumerable CreateFeedRanges( + // string minHexValue, + // string maxHexValue, + // int numberOfRanges = 10) + //{ + // if (minHexValue == string.Empty) + // { + // minHexValue = "0"; + // } + + // // Convert hex strings to ulong + // ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); + // ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); + + // ulong range = maxValue - minValue + 1; // Include the upper boundary + // ulong stepSize = range / (ulong)numberOfRanges; + + // // Generate the sub-ranges + // List<(string, string)> subRanges = new(); + // ulong splitMaxValue = default; + + // for (int i = 0; i < numberOfRanges; i++) + // { + // ulong splitMinValue = splitMaxValue; + // splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; + // subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); + // } + + // List feedRanges = new List(); + + // foreach ((string min, string max) in subRanges) + // { + // feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( + // min: min, + // max: max)); + // } + + // return feedRanges; + //} /// /// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. @@ -754,7 +762,7 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) /// Critical for invoking GetEPKRangeForPrefixPartitionKey. /// Critical for determining if the current changed lsn has been processed. /// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] - private static bool HasChangeBeedProcessed( + private static bool HasChangeBeenProcessed( ContainerInternal monitoredContainer, FeedRange feedRange, long lsnOfChange, @@ -769,17 +777,13 @@ private static bool HasChangeBeedProcessed( return false; } - Logger.LogLine($"{nameof(feedRange)} -> {feedRange.ToJsonString()}"); - Logger.LogLine($"{nameof(lsnOfChange)} -> {lsnOfChange}"); - Logger.LogLine($"{nameof(bookmarks)} -> {JsonConvert.SerializeObject(bookmarks)}"); - foreach (FeedRange overlappingRange in overlappingRangesFromFeedRange) { - foreach ((FeedRange range, long lsn) in bookmarks.Select(x => x)) + foreach ((FeedRange range, long lsn) in bookmarks.Select(bookmark => bookmark)) { if (lsnOfChange <= lsn && overlappingRange.Equals(range)) { - Logger.LogLine($"The range '{range}' with lsn '{lsn}' has been processed."); + Logger.LogLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); return true; } @@ -790,7 +794,7 @@ private static bool HasChangeBeedProcessed( } - private static async Task HasChangeBeedProcessedAsync( + private static async Task HasChangeBeenProcessedAsync( ContainerInternal monitoredContainer, PartitionKey partitionKey, long lsnOfChange, @@ -805,17 +809,13 @@ private static async Task HasChangeBeedProcessedAsync( return false; } - Logger.LogLine($"{nameof(partitionKey)} -> {partitionKey.ToJsonString()}"); - Logger.LogLine($"{nameof(lsnOfChange)} -> {lsnOfChange}"); - Logger.LogLine($"{nameof(bookmarks)} -> {JsonConvert.SerializeObject(bookmarks)}"); - foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey) { - foreach ((FeedRange range, long lsn) in bookmarks.Select(x => x)) + foreach ((FeedRange range, long lsn) in bookmarks.Select(bookmark => bookmark)) { if (lsnOfChange <= lsn && overlappingRange.Equals(range)) { - Logger.LogLine($"The range '{range}' with lsn '{lsn}' has been processed."); + Logger.LogLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); return true; } From caff01e3d0d6646faa66cf07aa80aceff4673d17 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 12:42:47 -0400 Subject: [PATCH 022/145] add cancellation token to test. --- ...dProcessorBuilderWithAllVersionsAndDeletesTests.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 7febfe305d..fe3053d85c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -270,13 +270,14 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs partitionKey: partitionKey, monitoredContainer: monitoredContainer, lsnOfChange: lsnOfChange, - bookmarks: bookmarks); + bookmarks: bookmarks, + cancellationToken: token); if (!hasChangedBeenProcessed) { // 7.a. Update bookmarks if not been processed so that it is picked up next time. bookmarks.Add((feedRange, lsnOfChange)); - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = document.Current.id.ToString(), pk = document.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey); + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = document.Current.id.ToString(), pk = document.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); await Task.Delay(1000); } else @@ -798,11 +799,13 @@ private static async Task HasChangeBeenProcessedAsync( ContainerInternal monitoredContainer, PartitionKey partitionKey, long lsnOfChange, - IReadOnlyList<(FeedRange range, long lsn)> bookmarks) + IReadOnlyList<(FeedRange range, long lsn)> bookmarks, + CancellationToken cancellationToken) { IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync( partitionKey, - bookmarks.Select(bookmark => bookmark.range).ToList()); + bookmarks.Select(bookmark => bookmark.range).ToList(), + cancellationToken); if (overlappingRangesFromPartitionKey == null) { From 4cf2fea5bf995bdd39e8d016ff3a725ea1f3c2b3 Mon Sep 17 00:00:00 2001 From: Philip Thomas Date: Tue, 23 Jul 2024 12:49:56 -0400 Subject: [PATCH 023/145] just some test changes --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 143 +++++++++--------- 1 file changed, 69 insertions(+), 74 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index fe3053d85c..36217a6a30 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -259,7 +259,8 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs PartitionKey partitionKey = new(document.Current.pk.ToString()); // 4. Bookmarks - List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateBookmarksIfNotExists(document); + List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .CreateBookmarksIfNotExists(document); // 5. Log Logger.LogLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChange}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); @@ -278,7 +279,7 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs // 7.a. Update bookmarks if not been processed so that it is picked up next time. bookmarks.Add((feedRange, lsnOfChange)); await monitoredContainer.UpsertItemAsync(new { bookmarks, id = document.Current.id.ToString(), pk = document.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. } else { @@ -305,16 +306,10 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "original test" }, partitionKey: new PartitionKey("WA")); - await Task.Delay(1000); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. await monitoredContainer.CreateItemAsync(new { id = "2", pk = "GA", description = "original test" }, partitionKey: new PartitionKey("GA")); - await Task.Delay(1000); - - //await monitoredContainer.UpsertItemAsync(new { id = "1", pk = "WA", description = "test after replace" }, partitionKey: new PartitionKey("WA")); - //await Task.Delay(1000); - - //await monitoredContainer.UpsertItemAsync(new { id = "2", pk = "GA", description = "test after replace" }, partitionKey: new PartitionKey("GA")); - //await Task.Delay(1000); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); @@ -343,28 +338,6 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs return bookmarks; } - - //private static async Task> CreateTestBookmarksAsync( - // CosmosClient cosmosClient, - // string containerRId) - //{ - // Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - // IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - // collectionRid: containerRId, - // range: FeedRangeEpk.FullRange.Range, - // trace: NoOpTrace.Singleton, - // forceRefresh: true); - - // IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( - // minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, - // maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, - // numberOfRanges: 3); - - // return bookmarkRanges - // .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) - // .ToList(); - - //} /// /// This is based on an issue located at . @@ -715,48 +688,70 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) FeedRangeEpk feedRangeEpk = new(range); return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); - } - - //private static IEnumerable CreateFeedRanges( - // string minHexValue, - // string maxHexValue, - // int numberOfRanges = 10) - //{ - // if (minHexValue == string.Empty) - // { - // minHexValue = "0"; - // } - - // // Convert hex strings to ulong - // ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); - // ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); - - // ulong range = maxValue - minValue + 1; // Include the upper boundary - // ulong stepSize = range / (ulong)numberOfRanges; - - // // Generate the sub-ranges - // List<(string, string)> subRanges = new(); - // ulong splitMaxValue = default; - - // for (int i = 0; i < numberOfRanges; i++) - // { - // ulong splitMinValue = splitMaxValue; - // splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; - // subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); - // } - - // List feedRanges = new List(); - - // foreach ((string min, string max) in subRanges) - // { - // feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( - // min: min, - // max: max)); - // } - - // return feedRanges; - //} - + } + + //private static async Task> CreateTestBookmarksAsync( + // CosmosClient cosmosClient, + // string containerRId) + //{ + // Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + // IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + // collectionRid: containerRId, + // range: FeedRangeEpk.FullRange.Range, + // trace: NoOpTrace.Singleton, + // forceRefresh: true); + + // IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( + // minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, + // maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, + // numberOfRanges: 3); + + // return bookmarkRanges + // .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) + // .ToList(); + + //} + + //private static IEnumerable CreateFeedRanges( + // string minHexValue, + // string maxHexValue, + // int numberOfRanges = 10) + //{ + // if (minHexValue == string.Empty) + // { + // minHexValue = "0"; + // } + + // // Convert hex strings to ulong + // ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); + // ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); + + // ulong range = maxValue - minValue + 1; // Include the upper boundary + // ulong stepSize = range / (ulong)numberOfRanges; + + // // Generate the sub-ranges + // List<(string, string)> subRanges = new(); + // ulong splitMaxValue = default; + + // for (int i = 0; i < numberOfRanges; i++) + // { + // ulong splitMinValue = splitMaxValue; + // splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; + // subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); + // } + + // List feedRanges = new List(); + + // foreach ((string min, string max) in subRanges) + // { + // feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( + // min: min, + // max: max)); + // } + + // return feedRanges; + //} + /// /// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. /// From 0c5617902111f2f849342a647423936f083a539a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 13:10:27 -0400 Subject: [PATCH 024/145] fix issue with FeedRangeEpk --- .../src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs | 3 +++ Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs index 5fc77cd8d1..8cd684f5eb 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs @@ -24,6 +24,9 @@ public abstract class ChangeFeedProcessorContext /// public abstract Headers Headers { get; } + /// + /// Gets the feed range. + /// #if PREVIEW public #else diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index b9d02ecc85..445a05339d 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -478,12 +478,7 @@ internal static SubStatusCodes GetSubStatusCodes(string value) return null; } -#if PREVIEW - public -#else - internal -#endif - virtual FeedRangeEpk FeedRangeEpk + internal virtual FeedRangeEpk FeedRangeEpk { get; set; From 563ddb9d5574153d9be09dd0b515a3f09c1944b4 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 14:39:28 -0400 Subject: [PATCH 025/145] some test changes from meeting. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 130 +++++++----------- 1 file changed, 48 insertions(+), 82 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 36217a6a30..366bf30037 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -243,47 +243,64 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { - foreach (ChangeFeedItem document in docs) + foreach (ChangeFeedItem changedDocument in docs) { - // NOTE(philipthomas-MSFT): Get all that we need, FeedRange, LsnOfChange, PartitionKey, and Bookmarks if exist. - + // NOTE(philipthomas-MSFT): Get all that we need, FeedRange, LsnOfChangedDocument, PartitionKey, and Bookmarks if exist. + // How can we replay? Restart the processor and read documents. + + Logger.LogLine($"document: {JsonConvert.SerializeObject(changedDocument)}"); + // 1.) FeedRange FeedRange feedRange = context.FeedRange; - // 2.) LsnOfChange - _ = long.TryParse( - s: context.Headers.ContinuationToken.Trim('"'), - result: out long lsnOfChange); + // 2.) LsnOfChangedDocument + long lsnOfChangedDocument = changedDocument.Metadata.Lsn; // 3.) PartitionKey - PartitionKey partitionKey = new(document.Current.pk.ToString()); + PartitionKey partitionKey = new(changedDocument.Current.pk.ToString()); - // 4. Bookmarks + // 4. Bookmarks. The customer will write their own CreateBookmarksIfNotExists logic but can yse this as a model. List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .CreateBookmarksIfNotExists(document); + .CreateBookmarksIfNotExists(changedDocument); // 5. Log - Logger.LogLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChange}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + Logger.LogLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests .HasChangeBeenProcessedAsync( - partitionKey: partitionKey, + partitionKey: partitionKey, + feedRange: feedRange, monitoredContainer: monitoredContainer, - lsnOfChange: lsnOfChange, + lsnOfChange: lsnOfChangedDocument, bookmarks: bookmarks, cancellationToken: token); if (!hasChangedBeenProcessed) { // 7.a. Update bookmarks if not been processed so that it is picked up next time. - bookmarks.Add((feedRange, lsnOfChange)); - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = document.Current.id.ToString(), pk = document.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + (FeedRange range, long lsn) bookmark = bookmarks.FirstOrDefault(bookmark => bookmark.range.Equals(feedRange)); + + if (bookmark == default) + { + bookmarks.Add((feedRange, lsnOfChangedDocument)); + + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + } + else + { + bookmark.lsn = lsnOfChangedDocument; + + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + } } else { - // 7.b. Do nothing? + // 7.b. Customer maybe log these, but nothing else. + + Logger.LogLine($"hasChangedBeenProcessed"); } } @@ -690,68 +707,6 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); } - //private static async Task> CreateTestBookmarksAsync( - // CosmosClient cosmosClient, - // string containerRId) - //{ - // Routing.PartitionKeyRangeCache partitionKeyRangeCache = await cosmosClient.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - // IReadOnlyList currentContainerRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - // collectionRid: containerRId, - // range: FeedRangeEpk.FullRange.Range, - // trace: NoOpTrace.Singleton, - // forceRefresh: true); - - // IEnumerable bookmarkRanges = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRanges( - // minHexValue: currentContainerRanges.FirstOrDefault().MinInclusive, - // maxHexValue: currentContainerRanges.LastOrDefault().MaxExclusive, - // numberOfRanges: 3); - - // return bookmarkRanges - // .Select((bookmarkRange, index) => (bookmarkRange, lsn: (long)(index + 1) * 25)) - // .ToList(); - - //} - - //private static IEnumerable CreateFeedRanges( - // string minHexValue, - // string maxHexValue, - // int numberOfRanges = 10) - //{ - // if (minHexValue == string.Empty) - // { - // minHexValue = "0"; - // } - - // // Convert hex strings to ulong - // ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); - // ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); - - // ulong range = maxValue - minValue + 1; // Include the upper boundary - // ulong stepSize = range / (ulong)numberOfRanges; - - // // Generate the sub-ranges - // List<(string, string)> subRanges = new(); - // ulong splitMaxValue = default; - - // for (int i = 0; i < numberOfRanges; i++) - // { - // ulong splitMinValue = splitMaxValue; - // splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; - // subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); - // } - - // List feedRanges = new List(); - - // foreach ((string min, string max) in subRanges) - // { - // feedRanges.Add(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.CreateFeedRange( - // min: min, - // max: max)); - // } - - // return feedRanges; - //} - /// /// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. /// @@ -791,7 +746,8 @@ private static bool HasChangeBeenProcessed( } private static async Task HasChangeBeenProcessedAsync( - ContainerInternal monitoredContainer, + ContainerInternal monitoredContainer, + FeedRange feedRange, PartitionKey partitionKey, long lsnOfChange, IReadOnlyList<(FeedRange range, long lsn)> bookmarks, @@ -806,8 +762,19 @@ private static async Task HasChangeBeenProcessedAsync( { return false; } + + IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( + feedRange, + bookmarks.Select(bookmark => bookmark.range).ToList()); - foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey) + if (overlappingRangesFromFeedRange == null) + { + return false; + } + + Logger.LogLine($"PartitionKey overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}; FeedRange overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); + + foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey.Concat(overlappingRangesFromFeedRange)) { foreach ((FeedRange range, long lsn) in bookmarks.Select(bookmark => bookmark)) { @@ -821,7 +788,6 @@ private static async Task HasChangeBeenProcessedAsync( } return false; - } } } From 73b912a34995ffd575fe6fe1986171bfb837a701 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 14:45:09 -0400 Subject: [PATCH 026/145] re-run update contracts. --- .../Contracts/DotNetPreviewSDKAPI.json | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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 9ea60dd372..78d3cbeb79 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 @@ -149,10 +149,10 @@ ], "MethodInfo": "System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { + "Void .ctor()": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" + "MethodInfo": "[Void .ctor(), Void .ctor()]" } }, "NestedTypes": {} @@ -243,6 +243,22 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.ChangeFeedProcessorContext;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.FeedRange FeedRange": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FeedRange;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.ComputedProperty;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { From 771c4873e62a17f4b163afd4bd50c33c7077e7c0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 16:24:23 -0400 Subject: [PATCH 027/145] making it work. --- .../src/Resource/Container/Container.cs | 40 +++++------ .../Resource/Container/ContainerCore.Items.cs | 66 ++++++++++++++++++ .../src/Resource/Container/ContainerCore.cs | 67 ------------------- .../Resource/Container/ContainerInlineCore.cs | 22 ++---- .../Resource/Container/ContainerInternal.cs | 4 ++ .../CosmosContainerTests.cs | 4 +- .../Contracts/DotNetPreviewSDKAPI.json | 10 +++ .../Contracts/DotNetSDKAPI.json | 10 --- 8 files changed, 109 insertions(+), 114 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index b2941a6c3b..2445ef2d96 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1652,24 +1652,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManu string processorName, ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); - /// - /// Takes a given list of ranges and find overlapping ranges for the given partition key. - /// - /// A given partition key. - /// A given list of ranges. - /// - /// A list of overlapping ranges for the the given partition key. - public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); - - /// - /// Takes a given list of ranges and find overlapping ranges for the given feed range. - /// - /// A given feed range. - /// A given list of ranges. - /// A list of overlapping ranges for the the given feed range epk. - public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); - -#if PREVIEW +#if PREVIEW /// /// Deletes all items in the Container with the specified value. /// Starts an asynchronous Cosmos DB background operation which deletes all items in the Container with the specified value. @@ -1771,7 +1754,24 @@ public abstract Task> GetPartitionKeyRangesAsync( /// An instance of public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, - ChangeFeedHandler> onChangesDelegate); -#endif + ChangeFeedHandler> onChangesDelegate); + + /// + /// Takes a given list of ranges and find overlapping ranges for the given partition key. + /// + /// A given partition key. + /// A given list of ranges. + /// + /// A list of overlapping ranges for the the given partition key. + public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); + + /// + /// Takes a given list of ranges and find overlapping ranges for the given feed range. + /// + /// A given feed range. + /// A given list of ranges. + /// A list of overlapping ranges for the the given feed range epk. + public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); +#endif } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 1c1300bdc2..bc03348cac 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1252,6 +1252,72 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( container: this, changeFeedProcessor: changeFeedProcessor, applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); + } + + public override async Task> FindOverlappingRangesAsync( + Cosmos.PartitionKey partitionKey, + IReadOnlyList feedRanges, + CancellationToken cancellationToken = default) + { + List overlappingRanges = new (); + + foreach (Documents.Routing.Range range in ContainerCore.ConvertToRange(feedRanges)) + { + if (Documents.Routing.Range.CheckOverlapping( + range1: await this.ConvertToRangeAsync( + partitionKey: partitionKey, + cancellationToken: cancellationToken), + range2: range)) + { + overlappingRanges.Add(new FeedRangeEpk(range)); + } + } + + return overlappingRanges; + } + + public override IReadOnlyList FindOverlappingRanges( + Cosmos.FeedRange feedRange, + IReadOnlyList feedRanges) + { + List overlappingRanges = new (); + + foreach (Documents.Routing.Range range in ContainerCore.ConvertToRange(feedRanges)) + { + if (Documents.Routing.Range.CheckOverlapping( + range1: ContainerCore.ConvertToRange(feedRange), + range2: range)) + { + overlappingRanges.Add(new FeedRangeEpk(range)); + } + } + + return overlappingRanges; + } + + private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) + { + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + Documents.Routing.Range range = Documents.Routing.Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); + return range; + } + + private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) + { + foreach (FeedRange fromFeedRange in fromFeedRanges) + { + yield return ContainerCore.ConvertToRange(fromFeedRange); + } + } + + private static Documents.Routing.Range ConvertToRange(FeedRange fromFeedRange) + { + if (fromFeedRange is not FeedRangeEpk feedRangeEpk) + { + return default; + } + + return feedRangeEpk.Range; } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 482937ccf3..6b3fdc8dce 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -697,72 +697,5 @@ public override FeedIterator GetChangeFeedIteratorWithQuery( changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); } - - public override async Task> FindOverlappingRangesAsync( - Cosmos.PartitionKey partitionKey, - IReadOnlyList feedRanges, - CancellationToken cancellationToken = default) - { - List overlappingRanges = new (); - - foreach (Range range in ContainerCore.ConvertToRange(feedRanges)) - { - if (Range.CheckOverlapping( - range1: await this.ConvertToRangeAsync( - partitionKey: partitionKey, - cancellationToken: cancellationToken), - range2: range)) - { - overlappingRanges.Add(new FeedRangeEpk(range)); - } - } - - return overlappingRanges; - } - - public override IReadOnlyList FindOverlappingRanges( - Cosmos.FeedRange feedRange, - IReadOnlyList feedRanges) - { - List overlappingRanges = new List(); - - foreach (Range range in ContainerCore.ConvertToRange(feedRanges)) - { - if (Range.CheckOverlapping( - range1: ContainerCore.ConvertToRange(feedRange), - range2: range)) - { - overlappingRanges.Add(new FeedRangeEpk(range)); - } - } - - return overlappingRanges; - } - - private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) - { - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - Range range = Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); - - return range; - } - - private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) - { - foreach (FeedRange fromFeedRange in fromFeedRanges) - { - yield return ContainerCore.ConvertToRange(fromFeedRange); - } - } - - private static Range ConvertToRange(FeedRange fromFeedRange) - { - if (fromFeedRange is not FeedRangeEpk feedRangeEpk) - { - return default; - } - - return feedRangeEpk.Range; - } } } \ 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 c62da82191..b9a8854a6d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -585,8 +585,8 @@ public override FeedIterator GetChangeFeedIterator( ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions = null) { - return new FeedIteratorInlineCore(base.GetChangeFeedIterator(changeFeedStartFrom, - changeFeedMode, + return new FeedIteratorInlineCore(base.GetChangeFeedIterator(changeFeedStartFrom, + changeFeedMode, changeFeedRequestOptions), this.ClientContext); } @@ -661,24 +661,14 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( openTelemetry: (response) => new OpenTelemetryResponse(response)); } - public override async Task> FindOverlappingRangesAsync( - Cosmos.PartitionKey partitionKey, - IReadOnlyList feedRanges, - CancellationToken cancellationToken = default) + public override IReadOnlyList FindOverlappingRanges(FeedRange feedRange, IReadOnlyList feedRanges) { - return await base.FindOverlappingRangesAsync( - partitionKey: partitionKey, - feedRanges: feedRanges, - cancellationToken: cancellationToken); + return base.FindOverlappingRanges(feedRange, feedRanges); } - public override IReadOnlyList FindOverlappingRanges( - Cosmos.FeedRange feedRange, - IReadOnlyList feedRanges) + public override async Task> FindOverlappingRangesAsync(PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) { - return base.FindOverlappingRanges( - feedRange: feedRange, - feedRanges: feedRanges); + return await base.FindOverlappingRangesAsync(partitionKey, feedRanges, cancellationToken); } } } \ 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 cedef3f3de..7ee6239bef 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -151,6 +151,10 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); + + public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); + + public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); #endif public abstract class TryExecuteQueryResult diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 8c67ee3f19..f44c2b4197 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1784,6 +1784,7 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString()); } +#if PREVIEW [TestMethod] [Owner("philipthomas-MSFT")] public async Task TestFindOverlappingRangesByPartitionKeyAsync() @@ -1872,6 +1873,7 @@ public async Task TestFindOverlappingRangesByFeedRangeAsync() await container.DeleteContainerAsync(); } } - } + } +#endif } } 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 78d3cbeb79..4d73783d56 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 @@ -320,6 +320,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, + "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], @@ -329,6 +334,11 @@ "Type": "Method", "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 02c91009e6..89074f0fef 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -1502,11 +1502,6 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.TransactionalBatch CreateTransactionalBatch(Microsoft.Azure.Cosmos.PartitionKey);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange])": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Linq.IOrderedQueryable`1[T] GetItemLinqQueryable[T](Boolean, System.String, Microsoft.Azure.Cosmos.QueryRequestOptions, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions)": { "Type": "Method", "Attributes": [], @@ -1637,11 +1632,6 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ThroughputResponse] ReplaceThroughputAsync(Microsoft.Azure.Cosmos.ThroughputProperties, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] GetFeedRangesAsync(System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], From 6559eb00995d38f06e06dbaab8b8f25c2d96dfc8 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 16:33:46 -0400 Subject: [PATCH 028/145] sdd to EncryptionContainer.cs --- .../src/EncryptionContainer.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index b898a77179..49cf4a2cf6 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,7 +1027,24 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } + + public override async Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) + { + return await this.container.FindOverlappingRangesAsync( + partitionKey, + feedRanges, + cancellationToken); + } + + public override IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges) + { + return this.container.FindOverlappingRanges( + feedRange, + feedRanges); + } + #endif + private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, ReadManyRequestOptions readManyRequestOptions = null, From 5e2a0b625dd541afec4a58a346d37bd5e49ad97e Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 23 Jul 2024 18:16:40 -0400 Subject: [PATCH 029/145] some formatting and change to Debug for logging before Logger.LogLine emits logs twice making it confusing to look at. --- .../ChangeFeedObserverContextCore.cs | 2 +- ...orBuilderWithAllVersionsAndDeletesTests.cs | 160 +++++++++++++----- 2 files changed, 116 insertions(+), 46 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs index 08e211e940..04102c64b9 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs @@ -42,7 +42,7 @@ internal ChangeFeedObserverContextCore( #else internal #endif - FeedRange FeedRange => new FeedRangeEpk(this.responseMessage.Headers.FeedRangeEpk.Range); + FeedRange FeedRange => new FeedRangeEpk(this.Headers.FeedRangeEpk.Range); public async Task CheckpointAsync() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 366bf30037..db6c43e7c4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -239,6 +239,7 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new (false); Exception exception = default; + DateTime dateTime = DateTime.Now; ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => @@ -248,7 +249,7 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs // NOTE(philipthomas-MSFT): Get all that we need, FeedRange, LsnOfChangedDocument, PartitionKey, and Bookmarks if exist. // How can we replay? Restart the processor and read documents. - Logger.LogLine($"document: {JsonConvert.SerializeObject(changedDocument)}"); + Debug.WriteLine($"changedDocument: {JsonConvert.SerializeObject(changedDocument)}"); // 1.) FeedRange FeedRange feedRange = context.FeedRange; @@ -264,7 +265,7 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs .CreateBookmarksIfNotExists(changedDocument); // 5. Log - Logger.LogLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + Debug.WriteLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -290,7 +291,11 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs } else { + bookmarks.Remove(bookmark); bookmark.lsn = lsnOfChangedDocument; + bookmarks.Add(bookmark); + //Debug.WriteLine($"bookmark.lsn: {bookmark.lsn}"); + //Debug.WriteLine($"bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. @@ -300,7 +305,7 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs { // 7.b. Customer maybe log these, but nothing else. - Logger.LogLine($"hasChangedBeenProcessed"); + Debug.WriteLine($"hasChangedBeenProcessed"); } } @@ -325,12 +330,17 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "original test" }, partitionKey: new PartitionKey("WA")); await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - await monitoredContainer.CreateItemAsync(new { id = "2", pk = "GA", description = "original test" }, partitionKey: new PartitionKey("GA")); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + //await monitoredContainer.CreateItemAsync(new { id = "2", pk = "GA", description = "original test" }, partitionKey: new PartitionKey("GA")); + //await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); await processor.StopAsync(); + + await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.ReplayCFPAsync( + monitoredContainer, + this.LeaseContainer, + dateTime); if (exception != default) { @@ -338,6 +348,104 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs } } + private static async Task ReplayCFPAsync( + ContainerInternal monitoredContainer, + Container leaseContainer, + DateTime startTime) + { + Logger.LogLine($"=== ReplayCFPAsync ==="); + + ManualResetEvent allDocsProcessed = new(false); + Exception exception = default; + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + foreach (ChangeFeedItem changedDocument in docs) + { + // 1.) FeedRange + FeedRange feedRange = context.FeedRange; + + // 2.) LsnOfChangedDocument + long lsnOfChangedDocument = changedDocument.Metadata.Lsn; + + // 3.) PartitionKey + PartitionKey partitionKey = new(changedDocument.Current.pk.ToString()); + + // 4. Bookmarks. The customer will write their own CreateBookmarksIfNotExists logic but can yse this as a model. + List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .CreateBookmarksIfNotExists(changedDocument); + + // 5. Log + Debug.WriteLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + + // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. + bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests + .HasChangeBeenProcessedAsync( + partitionKey: partitionKey, + feedRange: feedRange, + monitoredContainer: monitoredContainer, + lsnOfChange: lsnOfChangedDocument, + bookmarks: bookmarks, + cancellationToken: token); + + if (!hasChangedBeenProcessed) + { + // 7.a. Update bookmarks if not been processed so that it is picked up next time. + (FeedRange range, long lsn) bookmark = bookmarks.FirstOrDefault(bookmark => bookmark.range.Equals(feedRange)); + + if (bookmark == default) + { + bookmarks.Add((feedRange, lsnOfChangedDocument)); + + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + } + else + { + bookmarks.Remove(bookmark); + bookmark.lsn = lsnOfChangedDocument; + bookmarks.Add(bookmark); + //Debug.WriteLine($"bookmark.lsn: {bookmark.lsn}"); + //Debug.WriteLine($"bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + + await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); + await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. + } + } + else + { + // 7.b. Customer maybe log these, but nothing else. + + Debug.WriteLine($"hasChangedBeenProcessed"); + } + } + + return; + }) + .WithStartTime(startTime) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(leaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + await processor.StartAsync(); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + await processor.StopAsync(); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + private static List<(FeedRange range, long lsn)> CreateBookmarksIfNotExists(ChangeFeedItem change) { List<(FeedRange range, long lsn)> bookmarks = new(); @@ -706,44 +814,6 @@ private static Cosmos.FeedRange CreateFeedRange(string min, string max) return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); } - - /// - /// Checks bookmark ranges using partitionKey's range to see if that current changed lsn has been processed. - /// - /// Critical for invoking GetEPKRangeForPrefixPartitionKey. - /// Critical for determining if the current changed lsn has been processed. - /// Critical for feed ranges with lsn from the bookmarks. [{ min, max, lsn }] - private static bool HasChangeBeenProcessed( - ContainerInternal monitoredContainer, - FeedRange feedRange, - long lsnOfChange, - IReadOnlyList<(FeedRange range, long lsn)> bookmarks) - { - IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( - feedRange: feedRange, - feedRanges: bookmarks.Select(bookmark => bookmark.range).ToList()); - - if (overlappingRangesFromFeedRange == null) - { - return false; - } - - foreach (FeedRange overlappingRange in overlappingRangesFromFeedRange) - { - foreach ((FeedRange range, long lsn) in bookmarks.Select(bookmark => bookmark)) - { - if (lsnOfChange <= lsn && overlappingRange.Equals(range)) - { - Logger.LogLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); - - return true; - } - } - } - - return false; - - } private static async Task HasChangeBeenProcessedAsync( ContainerInternal monitoredContainer, @@ -772,7 +842,7 @@ private static async Task HasChangeBeenProcessedAsync( return false; } - Logger.LogLine($"PartitionKey overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}; FeedRange overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); + //Debug.WriteLine($"PartitionKey overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}; FeedRange overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey.Concat(overlappingRangesFromFeedRange)) { @@ -780,7 +850,7 @@ private static async Task HasChangeBeenProcessedAsync( { if (lsnOfChange <= lsn && overlappingRange.Equals(range)) { - Logger.LogLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); + Debug.WriteLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); return true; } From bda2f44313c31d2c36e0787d44b6398af7f9589c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 13:49:10 -0400 Subject: [PATCH 030/145] test update --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 280 ++++++------------ 1 file changed, 91 insertions(+), 189 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index db6c43e7c4..94c591d788 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -7,11 +7,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System; using System.Collections.Generic; using System.Diagnostics; - using System.Linq; + using System.Linq; using System.Threading; - using System.Threading.Tasks; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; - using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -234,79 +235,58 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() [Owner("philipthomas-MSFT")] [Description("Scenario: When documents are created, the document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. " + "The context header also now has the FeedRange included. This is simulating a customer scenario using HasChangeBeenProcessed and FindOverlappingRanges.")] - public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAsync() + public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverlappingRangesTestsAsync() { (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new (false); - Exception exception = default; - DateTime dateTime = DateTime.Now; + Exception exception = default; + int readDocumentCount = 0; ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - foreach (ChangeFeedItem changedDocument in docs) - { - // NOTE(philipthomas-MSFT): Get all that we need, FeedRange, LsnOfChangedDocument, PartitionKey, and Bookmarks if exist. - // How can we replay? Restart the processor and read documents. - - Debug.WriteLine($"changedDocument: {JsonConvert.SerializeObject(changedDocument)}"); - - // 1.) FeedRange - FeedRange feedRange = context.FeedRange; + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => + { + readDocumentCount += docs.Count; + Assert.IsTrue(docs.Count > 0); - // 2.) LsnOfChangedDocument - long lsnOfChangedDocument = changedDocument.Metadata.Lsn; - - // 3.) PartitionKey - PartitionKey partitionKey = new(changedDocument.Current.pk.ToString()); + FeedRange feedRange = context.FeedRange; + Assert.IsNotNull(feedRange); - // 4. Bookmarks. The customer will write their own CreateBookmarksIfNotExists logic but can yse this as a model. - List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .CreateBookmarksIfNotExists(changedDocument); + Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + collectionRid: containerResponse.Resource.ResourceId, + range: FeedRangeEpk.FullRange.Range, + trace: NoOpTrace.Singleton, + forceRefresh: true); - // 5. Log - Debug.WriteLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); - - // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. - bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .HasChangeBeenProcessedAsync( - partitionKey: partitionKey, - feedRange: feedRange, - monitoredContainer: monitoredContainer, - lsnOfChange: lsnOfChangedDocument, - bookmarks: bookmarks, - cancellationToken: token); - - if (!hasChangedBeenProcessed) - { - // 7.a. Update bookmarks if not been processed so that it is picked up next time. - (FeedRange range, long lsn) bookmark = bookmarks.FirstOrDefault(bookmark => bookmark.range.Equals(feedRange)); + List ranges = partitionKeyRanges + .Select(partitionKeyRange => new FeedRangeEpk(partitionKeyRange.ToRange())) + .Select(feedRangeEpk => feedRangeEpk as FeedRange) + .ToList(); - if (bookmark == default) - { - bookmarks.Add((feedRange, lsnOfChangedDocument)); + IReadOnlyList feedRangeOverlappingRanges = monitoredContainer.FindOverlappingRanges( + feedRange: feedRange, + feedRanges: ranges); - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - } - else - { - bookmarks.Remove(bookmark); - bookmark.lsn = lsnOfChangedDocument; - bookmarks.Add(bookmark); - //Debug.WriteLine($"bookmark.lsn: {bookmark.lsn}"); - //Debug.WriteLine($"bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); + Assert.IsNotNull(feedRangeOverlappingRanges); + Assert.AreEqual(expected: 1, actual: feedRangeOverlappingRanges.Count); - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - } - } - else - { - // 7.b. Customer maybe log these, but nothing else. + foreach (ChangeFeedItem doc in docs) + { + PartitionKey partitionKey = new(doc.Current.pk.ToString()); + IReadOnlyList partitionKeyOverlappingRanges = await monitoredContainer.FindOverlappingRangesAsync( + partitionKey: partitionKey, + feedRanges: ranges, + cancellationToken: cancellationToken); + + Assert.IsNotNull(partitionKeyOverlappingRanges); + Assert.AreEqual( + expected: 1, + actual: partitionKeyOverlappingRanges.Count); + } - Debug.WriteLine($"hasChangedBeenProcessed"); - } + if (readDocumentCount == 1) + { + allDocsProcessed.Set(); } return; @@ -327,142 +307,18 @@ public async Task WhenADocumentIsCreatedThenCheckIfChangeHasBeenProcessedTestsAs await processor.StartAsync(); await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "original test" }, partitionKey: new PartitionKey("WA")); + await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "" }, partitionKey: new PartitionKey("WA")); await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - - //await monitoredContainer.CreateItemAsync(new { id = "2", pk = "GA", description = "original test" }, partitionKey: new PartitionKey("GA")); - //await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); await processor.StopAsync(); - - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.ReplayCFPAsync( - monitoredContainer, - this.LeaseContainer, - dateTime); if (exception != default) { Assert.Fail(exception.ToString()); } } - - private static async Task ReplayCFPAsync( - ContainerInternal monitoredContainer, - Container leaseContainer, - DateTime startTime) - { - Logger.LogLine($"=== ReplayCFPAsync ==="); - - ManualResetEvent allDocsProcessed = new(false); - Exception exception = default; - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - foreach (ChangeFeedItem changedDocument in docs) - { - // 1.) FeedRange - FeedRange feedRange = context.FeedRange; - - // 2.) LsnOfChangedDocument - long lsnOfChangedDocument = changedDocument.Metadata.Lsn; - - // 3.) PartitionKey - PartitionKey partitionKey = new(changedDocument.Current.pk.ToString()); - - // 4. Bookmarks. The customer will write their own CreateBookmarksIfNotExists logic but can yse this as a model. - List<(FeedRange range, long lsn)> bookmarks = GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .CreateBookmarksIfNotExists(changedDocument); - - // 5. Log - Debug.WriteLine($"FeedRange: {feedRange.ToJsonString()}; LSN: {lsnOfChangedDocument}; PartitionKey: {partitionKey.ToJsonString()}; Bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); - - // 6. HasChangeBeenProcessed. The customer will write their own HasChangeBeedProcessedAsync logic but can use this as a model. - bool hasChangedBeenProcessed = await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .HasChangeBeenProcessedAsync( - partitionKey: partitionKey, - feedRange: feedRange, - monitoredContainer: monitoredContainer, - lsnOfChange: lsnOfChangedDocument, - bookmarks: bookmarks, - cancellationToken: token); - - if (!hasChangedBeenProcessed) - { - // 7.a. Update bookmarks if not been processed so that it is picked up next time. - (FeedRange range, long lsn) bookmark = bookmarks.FirstOrDefault(bookmark => bookmark.range.Equals(feedRange)); - - if (bookmark == default) - { - bookmarks.Add((feedRange, lsnOfChangedDocument)); - - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - } - else - { - bookmarks.Remove(bookmark); - bookmark.lsn = lsnOfChangedDocument; - bookmarks.Add(bookmark); - //Debug.WriteLine($"bookmark.lsn: {bookmark.lsn}"); - //Debug.WriteLine($"bookmarks: {JsonConvert.SerializeObject(bookmarks)}"); - - await monitoredContainer.UpsertItemAsync(new { bookmarks, id = changedDocument.Current.id.ToString(), pk = changedDocument.Current.pk.ToString(), description = "original test" }, partitionKey: partitionKey, cancellationToken: token); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - } - } - else - { - // 7.b. Customer maybe log these, but nothing else. - - Debug.WriteLine($"hasChangedBeenProcessed"); - } - } - - return; - }) - .WithStartTime(startTime) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(leaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }) - .Build(); - - await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - await processor.StopAsync(); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - - private static List<(FeedRange range, long lsn)> CreateBookmarksIfNotExists(ChangeFeedItem change) - { - List<(FeedRange range, long lsn)> bookmarks = new(); - - if (change.Current.bookmarks != null) - { - foreach (dynamic bookmark in change.Current.bookmarks) - { - JObject asJObject = JObject.Parse(bookmark.ToString()); - - bookmarks.Add((FeedRange.FromJsonString(asJObject["Item1"].ToString()), long.Parse(asJObject["Item2"].ToString()))); - } - - } - - return bookmarks; - } /// /// This is based on an issue located at . @@ -668,6 +524,52 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests allDocsProcessed: allDocsProcessed, withStartFromBeginning: withStartFromBeginning); } + + private static async Task ChangeLeaseStateAsync( + Container leaseContainer) + { + FeedIterator iterator = leaseContainer.GetItemQueryStreamIterator( + queryText: "SELECT * FROM c", + continuationToken: null); + + List leases = new List(); + while (iterator.HasMoreResults) + { + using (ResponseMessage responseMessage = await iterator.ReadNextAsync().ConfigureAwait(false)) + { + responseMessage.EnsureSuccessStatusCode(); + leases.AddRange(CosmosFeedResponseSerializer.FromFeedResponseStream( + serializerCore: CosmosContainerExtensions.DefaultJsonSerializer, + streamWithServiceEnvelope: responseMessage.Content)); + } + } + + int counter = 0; + + foreach (JObject lease in leases) + { + if (!lease.ContainsKey("Mode")) + { + continue; + } + + counter++; + + if (!string.IsNullOrWhiteSpace(lease["ContinuationToken"].Value())) + { + + //Logger.LogLine($"before change lease -> {JsonConvert.SerializeObject(lease)}"); + + lease["ContinuationToken"] = "\"0\""; + + _ = await leaseContainer.UpsertItemAsync(item: lease); + + //Logger.LogLine($"after change lease -> {JsonConvert.SerializeObject(lease)}"); + } + + } + } + private static async Task RevertLeaseDocumentsToLegacyWithNoMode( Container leaseContainer, From b02f35a9aea64a89e447d09e5ffe7854251d97bf Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 14:47:36 -0400 Subject: [PATCH 031/145] fix EncryptionContainer --- .../src/EncryptionContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index 64c7dc36a1..e052cc0ac6 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -762,6 +762,16 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV { throw new NotImplementedException(); } + + public override async Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public override IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges) + { + throw new NotImplementedException(); + } #endif /// /// This function handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy. From b5a65c8b6caed83a14f933c8dcfc10a325646b17 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 14:53:01 -0400 Subject: [PATCH 032/145] remove a test helper method that is no longer needed. and updated test description. --- ...orBuilderWithAllVersionsAndDeletesTests.cs | 57 +++---------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 94c591d788..3d3aaee14c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -234,7 +234,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() [TestMethod] [Owner("philipthomas-MSFT")] [Description("Scenario: When documents are created, the document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. " + - "The context header also now has the FeedRange included. This is simulating a customer scenario using HasChangeBeenProcessed and FindOverlappingRanges.")] + "The context also now has the FeedRange included. And using FeedRange and PartitionKey, testing FindOverlappingRanges.")] public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverlappingRangesTestsAsync() { (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); @@ -248,6 +248,8 @@ public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverla readDocumentCount += docs.Count; Assert.IsTrue(docs.Count > 0); + // NOTE(philipthomas-MSFT): New FeedRange on ChangeFeedProcessorContext. + FeedRange feedRange = context.FeedRange; Assert.IsNotNull(feedRange); @@ -263,6 +265,8 @@ public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverla .Select(feedRangeEpk => feedRangeEpk as FeedRange) .ToList(); + // NOTE(philipthomas-MSFT): New FindOverlappingRanges using FeedRange. + IReadOnlyList feedRangeOverlappingRanges = monitoredContainer.FindOverlappingRanges( feedRange: feedRange, feedRanges: ranges); @@ -273,6 +277,8 @@ public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverla foreach (ChangeFeedItem doc in docs) { PartitionKey partitionKey = new(doc.Current.pk.ToString()); + + // NOTE(philipthomas-MSFT): New FindOverlappingRangesAsync using PartitionKey. IReadOnlyList partitionKeyOverlappingRanges = await monitoredContainer.FindOverlappingRangesAsync( partitionKey: partitionKey, feedRanges: ranges, @@ -349,8 +355,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests allDocsProcessed: allDocsProcessed)); Assert.AreEqual(expected: "Switching ChangeFeedMode Incremental Feed to Full-Fidelity Feed is not allowed.", actual: exception.Message); - } - + } /// /// This is based on an issue located at . @@ -523,53 +528,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, withStartFromBeginning: withStartFromBeginning); - } - - private static async Task ChangeLeaseStateAsync( - Container leaseContainer) - { - FeedIterator iterator = leaseContainer.GetItemQueryStreamIterator( - queryText: "SELECT * FROM c", - continuationToken: null); - - List leases = new List(); - while (iterator.HasMoreResults) - { - using (ResponseMessage responseMessage = await iterator.ReadNextAsync().ConfigureAwait(false)) - { - responseMessage.EnsureSuccessStatusCode(); - leases.AddRange(CosmosFeedResponseSerializer.FromFeedResponseStream( - serializerCore: CosmosContainerExtensions.DefaultJsonSerializer, - streamWithServiceEnvelope: responseMessage.Content)); - } - } - - int counter = 0; - - foreach (JObject lease in leases) - { - if (!lease.ContainsKey("Mode")) - { - continue; - } - - counter++; - - if (!string.IsNullOrWhiteSpace(lease["ContinuationToken"].Value())) - { - - //Logger.LogLine($"before change lease -> {JsonConvert.SerializeObject(lease)}"); - - lease["ContinuationToken"] = "\"0\""; - - _ = await leaseContainer.UpsertItemAsync(item: lease); - - //Logger.LogLine($"after change lease -> {JsonConvert.SerializeObject(lease)}"); - } - - } } - private static async Task RevertLeaseDocumentsToLegacyWithNoMode( Container leaseContainer, From 9cee550b1c63233d4a1eed5b2c3b2103d8703087 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 15:59:48 -0400 Subject: [PATCH 033/145] trying to fix EncryptionContainer --- .../src/EncryptionContainer.cs | 16 ------- .../ChangeFeedProcessorBuilder.cs | 10 ++++ ...orBuilderWithAllVersionsAndDeletesTests.cs | 48 +++++++++++++++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 49cf4a2cf6..2b1873f8e4 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,22 +1027,6 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } - - public override async Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) - { - return await this.container.FindOverlappingRangesAsync( - partitionKey, - feedRanges, - cancellationToken); - } - - public override IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges) - { - return this.container.FindOverlappingRanges( - feedRange, - feedRanges); - } - #endif private async Task ReadManyItemsHelperAsync( diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs index 49bfa9d816..e2ab4b4f3f 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs @@ -132,6 +132,11 @@ public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval) /// The instance of to use. internal virtual ChangeFeedProcessorBuilder WithStartFromBeginning() { + if (this.changeFeedProcessorOptions.Mode == ChangeFeedMode.AllVersionsAndDeletes) + { + throw new NotImplementedException($"Using the 'WithStartFromBeginning' option with ChangeFeedProcessor is not supported with {ChangeFeedMode.AllVersionsAndDeletes} mode."); + } + this.changeFeedProcessorOptions.StartFromBeginning = true; return this; } @@ -149,6 +154,11 @@ internal virtual ChangeFeedProcessorBuilder WithStartFromBeginning() /// The instance of to use. public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime) { + if (this.changeFeedProcessorOptions.Mode == ChangeFeedMode.AllVersionsAndDeletes) + { + throw new NotImplementedException($"Using the 'WithStartTime' option with ChangeFeedProcessor is not supported with {ChangeFeedMode.AllVersionsAndDeletes} mode."); + } + if (startTime == null) { throw new ArgumentNullException(nameof(startTime)); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 3d3aaee14c..3b0b1aa802 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -325,6 +325,54 @@ public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverla Assert.Fail(exception.ToString()); } } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: WithStartTime should throw an exception when used in AVAD mode.")] + public async Task WhenACFPInAVADModeUsesWithStartTimeExpectExceptionTestsAsync() + { + (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + + NotImplementedException e = Assert.ThrowsException(() => + { + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + processorName: "processor", + onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => Task.CompletedTask) + .WithStartTime(DateTime.Now) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .Build(); + }); + + Assert.AreEqual( + expected: "Using the 'WithStartTime' option with ChangeFeedProcessor is not supported with Microsoft.Azure.Cosmos.ChangeFeed.ChangeFeedModeFullFidelity mode.", + actual: e.Message); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: WithStartFromBeginning should throw an exception when used in AVAD mode.")] + public async Task WhenACFPInAVADModeUsesWithStartFromBeginningExpectExceptionTestsAsync() + { + (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + + NotImplementedException e = Assert.ThrowsException(() => + { + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + processorName: "processor", + onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => Task.CompletedTask) + .WithStartFromBeginning() + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .Build(); + }); + + Assert.AreEqual( + expected: "Using the 'WithStartFromBeginning' option with ChangeFeedProcessor is not supported with Microsoft.Azure.Cosmos.ChangeFeed.ChangeFeedModeFullFidelity mode.", + actual: e.Message); + } /// /// This is based on an issue located at . From 1efe7142063bea1e8ccc1fd2fa3c1f69f99e87df Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 16:32:23 -0400 Subject: [PATCH 034/145] try, try again --- .../src/EncryptionContainer.cs | 15 +++++++++++++++ .../src/EncryptionContainer.cs | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 2b1873f8e4..c99d0bf597 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,6 +1027,21 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } + + public override Task> FindOverlappingRangesAsync( + Cosmos.PartitionKey partitionKey, + IReadOnlyList feedRanges, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public override IReadOnlyList FindOverlappingRanges( + Cosmos.FeedRange feedRange, + IReadOnlyList feedRanges) + { + throw new NotImplementedException(); + } #endif private async Task ReadManyItemsHelperAsync( diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index e052cc0ac6..5317a04797 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -763,12 +763,17 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV throw new NotImplementedException(); } - public override async Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) + public override Task> FindOverlappingRangesAsync( + Cosmos.PartitionKey partitionKey, + IReadOnlyList feedRanges, + CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public override IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges) + public override IReadOnlyList FindOverlappingRanges( + Cosmos.FeedRange feedRange, + IReadOnlyList feedRanges) { throw new NotImplementedException(); } From 0b0001bc04eb9c44a7b0bb48566209a276dd5833 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 16:47:10 -0400 Subject: [PATCH 035/145] hmm --- .../src/EncryptionContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index c99d0bf597..497d2cf209 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,7 +1027,9 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } +#endif +#if SDKPROJECTREF public override Task> FindOverlappingRangesAsync( Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, From 8fc35375a19e87e71c7dcb0f44fa8a09b919d9d4 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 29 Jul 2024 17:34:14 -0400 Subject: [PATCH 036/145] fixing a test. --- .../ChangeFeedProcessorContext.cs | 7 +------ .../Observers/ChangeFeedProcessorContextCore.cs | 7 +------ .../Contracts/DotNetPreviewSDKAPI.json | 16 ---------------- .../Contracts/DotNetSDKAPI.json | 10 ++++++++++ 4 files changed, 12 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs index 8cd684f5eb..0b2f893c64 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs @@ -27,11 +27,6 @@ public abstract class ChangeFeedProcessorContext /// /// Gets the feed range. /// -#if PREVIEW - public -#else - internal -#endif - abstract FeedRange FeedRange { get; } + public abstract FeedRange FeedRange { get; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs index 00f830ce79..8e9ca5f96c 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs @@ -22,11 +22,6 @@ public ChangeFeedProcessorContextCore(ChangeFeedObserverContextCore changeFeedOb public override Headers Headers => this.changeFeedObserverContextCore.Headers; -#if PREVIEW - public -#else - internal -#endif - override FeedRange FeedRange => this.changeFeedObserverContextCore.FeedRange; + public override FeedRange FeedRange => this.changeFeedObserverContextCore.FeedRange; } } \ 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 4d73783d56..c5e27b0ed7 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 @@ -243,22 +243,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.ChangeFeedProcessorContext;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Microsoft.Azure.Cosmos.FeedRange FeedRange": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FeedRange;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.ComputedProperty;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 89074f0fef..409454ca3b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -410,6 +410,16 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Microsoft.Azure.Cosmos.FeedRange FeedRange": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FeedRange;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.Headers get_Headers()": { "Type": "Method", "Attributes": [], From c84880097baed08c9fc3f76ed4d3201d742b959d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 1 Aug 2024 15:38:57 -0400 Subject: [PATCH 037/145] removing other features and boiling it down to just FindOverlappingRanges --- ...geFeedPartitionKeyResultSetIteratorCore.cs | 16 +- .../ChangeFeedProcessorBuilder.cs | 10 - .../ChangeFeedProcessorContext.cs | 7 +- .../ChangeFeedObserverContextCore.cs | 7 - .../ChangeFeedProcessorContextCore.cs | 4 +- Microsoft.Azure.Cosmos/src/Headers/Headers.cs | 8 +- ...orBuilderWithAllVersionsAndDeletesTests.cs | 264 +++--------------- .../Contracts/DotNetSDKAPI.json | 10 - 8 files changed, 50 insertions(+), 276 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs index c1664fc95d..0456fc1d87 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCore.cs @@ -4,11 +4,12 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed { - using System; + using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.LeaseManagement; + using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -58,15 +59,13 @@ public static ChangeFeedPartitionKeyResultSetIteratorCore Create( container: container, mode: mode, changeFeedStartFrom: startFrom, - options: requestOptions, - feedRangeEpk: lease?.FeedRange is FeedRangeEpk epk ? epk : default); + options: requestOptions); } private readonly CosmosClientContext clientContext; private readonly ChangeFeedRequestOptions changeFeedOptions; private readonly ChangeFeedMode mode; - private readonly FeedRangeEpk feedRangeEpk; private ChangeFeedStartFrom changeFeedStartFrom; private bool hasMoreResultsInternal; @@ -75,15 +74,13 @@ private ChangeFeedPartitionKeyResultSetIteratorCore( ContainerInternal container, ChangeFeedMode mode, ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedRequestOptions options, - FeedRangeEpk feedRangeEpk) + ChangeFeedRequestOptions options) { this.container = container ?? throw new ArgumentNullException(nameof(container)); this.mode = mode; this.changeFeedStartFrom = changeFeedStartFrom ?? throw new ArgumentNullException(nameof(changeFeedStartFrom)); this.clientContext = this.container.ClientContext; this.changeFeedOptions = options; - this.feedRangeEpk = feedRangeEpk; } public override bool HasMoreResults => this.hasMoreResultsInternal; @@ -142,10 +139,7 @@ public override async Task ReadNextAsync(ITrace trace, Cancella this.hasMoreResultsInternal = responseMessage.IsSuccessStatusCode; responseMessage.Headers.ContinuationToken = etag; this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(etag, (FeedRangeInternal)this.changeFeedStartFrom.FeedRange); - - // Set the FeedRangeEpk response header. - responseMessage.Headers.FeedRangeEpk = this.feedRangeEpk; - + return responseMessage; } } diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs index e2ab4b4f3f..49bfa9d816 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorBuilder.cs @@ -132,11 +132,6 @@ public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval) /// The instance of to use. internal virtual ChangeFeedProcessorBuilder WithStartFromBeginning() { - if (this.changeFeedProcessorOptions.Mode == ChangeFeedMode.AllVersionsAndDeletes) - { - throw new NotImplementedException($"Using the 'WithStartFromBeginning' option with ChangeFeedProcessor is not supported with {ChangeFeedMode.AllVersionsAndDeletes} mode."); - } - this.changeFeedProcessorOptions.StartFromBeginning = true; return this; } @@ -154,11 +149,6 @@ internal virtual ChangeFeedProcessorBuilder WithStartFromBeginning() /// The instance of to use. public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime) { - if (this.changeFeedProcessorOptions.Mode == ChangeFeedMode.AllVersionsAndDeletes) - { - throw new NotImplementedException($"Using the 'WithStartTime' option with ChangeFeedProcessor is not supported with {ChangeFeedMode.AllVersionsAndDeletes} mode."); - } - if (startTime == null) { throw new ArgumentNullException(nameof(startTime)); diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs index 0b2f893c64..4417b06861 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/ChangeFeedProcessorContext.cs @@ -22,11 +22,6 @@ public abstract class ChangeFeedProcessorContext /// /// Gets the headers related to the service response that provided the changes. /// - public abstract Headers Headers { get; } - - /// - /// Gets the feed range. - /// - public abstract FeedRange FeedRange { get; } + public abstract Headers Headers { get; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs index 04102c64b9..4615df9ecb 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedObserverContextCore.cs @@ -36,13 +36,6 @@ internal ChangeFeedObserverContextCore( public CosmosDiagnostics Diagnostics => this.responseMessage.Diagnostics; public Headers Headers => this.responseMessage.Headers; - -#if PREVIEW - public -#else - internal -#endif - FeedRange FeedRange => new FeedRangeEpk(this.Headers.FeedRangeEpk.Range); public async Task CheckpointAsync() { diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs index 8e9ca5f96c..cb4ceb9d2e 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Observers/ChangeFeedProcessorContextCore.cs @@ -20,8 +20,6 @@ public ChangeFeedProcessorContextCore(ChangeFeedObserverContextCore changeFeedOb public override CosmosDiagnostics Diagnostics => this.changeFeedObserverContextCore.Diagnostics; - public override Headers Headers => this.changeFeedObserverContextCore.Headers; - - public override FeedRange FeedRange => this.changeFeedObserverContextCore.FeedRange; + public override Headers Headers => this.changeFeedObserverContextCore.Headers; } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs index 445a05339d..f371383465 100644 --- a/Microsoft.Azure.Cosmos/src/Headers/Headers.cs +++ b/Microsoft.Azure.Cosmos/src/Headers/Headers.cs @@ -476,12 +476,6 @@ internal static SubStatusCodes GetSubStatusCodes(string value) } return null; - } - - internal virtual FeedRangeEpk FeedRangeEpk - { - get; - set; - } + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs index 3b0b1aa802..0479bbb353 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs @@ -7,12 +7,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System; using System.Collections.Generic; using System.Diagnostics; - using System.Linq; + using System.Linq; using System.Threading; - using System.Threading.Tasks; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; - using Microsoft.Azure.Cosmos.Services.Management.Tests; - using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -41,7 +40,7 @@ public async Task Cleanup() "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() { - (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); Exception exception = default; int ttlInSeconds = 5; Stopwatch stopwatch = new(); @@ -148,13 +147,39 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() { - (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new ManualResetEvent(false); Exception exception = default; ChangeFeedProcessor processor = monitoredContainer .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { + string id = default; + string pk = default; + string description = default; + + foreach (ChangeFeedItem change in docs) + { + if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) + { + id = change.Current.id.ToString(); + pk = change.Current.pk.ToString(); + description = change.Current.description.ToString(); + } + else + { + id = change.Previous.id.ToString(); + pk = change.Previous.pk.ToString(); + description = change.Previous.description.ToString(); + } + + ChangeFeedOperationType operationType = change.Metadata.OperationType; + long previousLsn = change.Metadata.PreviousLsn; + DateTime m = change.Metadata.ConflictResolutionTimestamp; + long lsn = change.Metadata.Lsn; + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + } + Assert.IsNotNull(context.LeaseToken); Assert.IsNotNull(context.Diagnostics); Assert.IsNotNull(context.Headers); @@ -231,149 +256,6 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } } - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Scenario: When documents are created, the document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode. " + - "The context also now has the FeedRange included. And using FeedRange and PartitionKey, testing FindOverlappingRanges.")] - public async Task WhenADocumentIsCreatedThenCheckFeedRangeInContextAndFindOverlappingRangesTestsAsync() - { - (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - ManualResetEvent allDocsProcessed = new (false); - Exception exception = default; - int readDocumentCount = 0; - - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: async (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => - { - readDocumentCount += docs.Count; - Assert.IsTrue(docs.Count > 0); - - // NOTE(philipthomas-MSFT): New FeedRange on ChangeFeedProcessorContext. - - FeedRange feedRange = context.FeedRange; - Assert.IsNotNull(feedRange); - - Routing.PartitionKeyRangeCache partitionKeyRangeCache = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - collectionRid: containerResponse.Resource.ResourceId, - range: FeedRangeEpk.FullRange.Range, - trace: NoOpTrace.Singleton, - forceRefresh: true); - - List ranges = partitionKeyRanges - .Select(partitionKeyRange => new FeedRangeEpk(partitionKeyRange.ToRange())) - .Select(feedRangeEpk => feedRangeEpk as FeedRange) - .ToList(); - - // NOTE(philipthomas-MSFT): New FindOverlappingRanges using FeedRange. - - IReadOnlyList feedRangeOverlappingRanges = monitoredContainer.FindOverlappingRanges( - feedRange: feedRange, - feedRanges: ranges); - - Assert.IsNotNull(feedRangeOverlappingRanges); - Assert.AreEqual(expected: 1, actual: feedRangeOverlappingRanges.Count); - - foreach (ChangeFeedItem doc in docs) - { - PartitionKey partitionKey = new(doc.Current.pk.ToString()); - - // NOTE(philipthomas-MSFT): New FindOverlappingRangesAsync using PartitionKey. - IReadOnlyList partitionKeyOverlappingRanges = await monitoredContainer.FindOverlappingRangesAsync( - partitionKey: partitionKey, - feedRanges: ranges, - cancellationToken: cancellationToken); - - Assert.IsNotNull(partitionKeyOverlappingRanges); - Assert.AreEqual( - expected: 1, - actual: partitionKeyOverlappingRanges.Count); - } - - if (readDocumentCount == 1) - { - allDocsProcessed.Set(); - } - - return; - }) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }) - .Build(); - - // Start the processor, insert 1 document to generate a checkpoint, and modify it. - // 1 second delay between operations to get different timestamps. - - await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "WA", description = "" }, partitionKey: new PartitionKey("WA")); - await Task.Delay(1000); // NOTE(philipthomas-MSFT): Not critical to delay this. - - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - await processor.StopAsync(); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Scenario: WithStartTime should throw an exception when used in AVAD mode.")] - public async Task WhenACFPInAVADModeUsesWithStartTimeExpectExceptionTestsAsync() - { - (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - - NotImplementedException e = Assert.ThrowsException(() => - { - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - processorName: "processor", - onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => Task.CompletedTask) - .WithStartTime(DateTime.Now) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .Build(); - }); - - Assert.AreEqual( - expected: "Using the 'WithStartTime' option with ChangeFeedProcessor is not supported with Microsoft.Azure.Cosmos.ChangeFeed.ChangeFeedModeFullFidelity mode.", - actual: e.Message); - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Scenario: WithStartFromBeginning should throw an exception when used in AVAD mode.")] - public async Task WhenACFPInAVADModeUsesWithStartFromBeginningExpectExceptionTestsAsync() - { - (ContainerInternal monitoredContainer, ContainerResponse containerResponse) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - - NotImplementedException e = Assert.ThrowsException(() => - { - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - processorName: "processor", - onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken cancellationToken) => Task.CompletedTask) - .WithStartFromBeginning() - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .Build(); - }); - - Assert.AreEqual( - expected: "Using the 'WithStartFromBeginning' option with ChangeFeedProcessor is not supported with Microsoft.Azure.Cosmos.ChangeFeed.ChangeFeedModeFullFidelity mode.", - actual: e.Message); - } - /// /// This is based on an issue located at . /// @@ -385,7 +267,7 @@ public async Task WhenACFPInAVADModeUsesWithStartFromBeginningExpectExceptionTes [DataRow(true)] public async Task WhenLatestVersionSwitchToAllVersionsAndDeletesExpectsAexceptionTestAsync(bool withStartFromBeginning) { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -403,7 +285,8 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests allDocsProcessed: allDocsProcessed)); Assert.AreEqual(expected: "Switching ChangeFeedMode Incremental Feed to Full-Fidelity Feed is not allowed.", actual: exception.Message); - } + } + /// /// This is based on an issue located at . @@ -416,7 +299,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenLegacyLatestVersionSwitchToAllVersionsAndDeletesExpectsAexceptionTestAsync(bool withStartFromBeginning) { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -454,7 +337,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenAllVersionsAndDeletesSwitchToLatestVersionExpectsAexceptionTestAsync(bool withStartFromBeginning) { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -483,7 +366,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests "no exception is expected.")] public async Task WhenNoSwitchAllVersionsAndDeletesFDoesNotExpectAexceptionTestAsync() { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new(false); try @@ -517,7 +400,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenNoSwitchLatestVersionDoesNotExpectAexceptionTestAsync(bool withStartFromBeginning) { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); try @@ -553,7 +436,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests [DataRow(true)] public async Task WhenLegacyNoSwitchLatestVersionDoesNotExpectAnExceptionTestAsync(bool withStartFromBeginning) { - (ContainerInternal monitoredContainer, ContainerResponse _) = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests @@ -576,7 +459,7 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, withStartFromBeginning: withStartFromBeginning); - } + } private static async Task RevertLeaseDocumentsToLegacyWithNoMode( Container leaseContainer, @@ -687,7 +570,7 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync } } - private async Task<(ContainerInternal, ContainerResponse)> CreateMonitoredContainer(ChangeFeedMode changeFeedMode) + private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) { string PartitionKey = "/pk"; ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), @@ -703,70 +586,7 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync throughput: 10000, cancellationToken: this.cancellationToken); - return ((ContainerInternal)response, response); - } - - private static Cosmos.FeedRange CreateFeedRange(string min, string max) - { - if (min == "0") - { - min = ""; - } - - Documents.Routing.Range range = new( - min: min, - max: max, - isMinInclusive: true, - isMaxInclusive: false); - - FeedRangeEpk feedRangeEpk = new(range); - - return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); - } - - private static async Task HasChangeBeenProcessedAsync( - ContainerInternal monitoredContainer, - FeedRange feedRange, - PartitionKey partitionKey, - long lsnOfChange, - IReadOnlyList<(FeedRange range, long lsn)> bookmarks, - CancellationToken cancellationToken) - { - IReadOnlyList overlappingRangesFromPartitionKey = await monitoredContainer.FindOverlappingRangesAsync( - partitionKey, - bookmarks.Select(bookmark => bookmark.range).ToList(), - cancellationToken); - - if (overlappingRangesFromPartitionKey == null) - { - return false; - } - - IReadOnlyList overlappingRangesFromFeedRange = monitoredContainer.FindOverlappingRanges( - feedRange, - bookmarks.Select(bookmark => bookmark.range).ToList()); - - if (overlappingRangesFromFeedRange == null) - { - return false; - } - - //Debug.WriteLine($"PartitionKey overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromPartitionKey)}; FeedRange overlapping ranges: {JsonConvert.SerializeObject(overlappingRangesFromFeedRange)}"); - - foreach (FeedRange overlappingRange in overlappingRangesFromPartitionKey.Concat(overlappingRangesFromFeedRange)) - { - foreach ((FeedRange range, long lsn) in bookmarks.Select(bookmark => bookmark)) - { - if (lsnOfChange <= lsn && overlappingRange.Equals(range)) - { - Debug.WriteLine($"The range '{range}' with lsn '{lsnOfChange}' has been processed."); - - return true; - } - } - } - - return false; + return (ContainerInternal)response; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index c6a6817f1d..2cc3fd8de5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -410,16 +410,6 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.FeedRange FeedRange": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange FeedRange;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.FeedRange get_FeedRange()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.FeedRange get_FeedRange();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Microsoft.Azure.Cosmos.Headers get_Headers()": { "Type": "Method", "Attributes": [], From 490fe03add798f153936b0fd1f1f58327a3905de Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 5 Aug 2024 14:33:49 -0400 Subject: [PATCH 038/145] move rangeFromPartitionKey out of loop. --- .../src/Resource/Container/ContainerCore.Items.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index bc03348cac..39516ab357 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1261,12 +1261,14 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( { List overlappingRanges = new (); + Documents.Routing.Range rangeFromPartitionKey = await this.ConvertToRangeAsync( + partitionKey: partitionKey, + cancellationToken: cancellationToken); + foreach (Documents.Routing.Range range in ContainerCore.ConvertToRange(feedRanges)) { if (Documents.Routing.Range.CheckOverlapping( - range1: await this.ConvertToRangeAsync( - partitionKey: partitionKey, - cancellationToken: cancellationToken), + range1: rangeFromPartitionKey, range2: range)) { overlappingRanges.Add(new FeedRangeEpk(range)); From e57ce1897d5ea8048cabead4a05289d702d58e48 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 13:35:44 -0400 Subject: [PATCH 039/145] IsSubset --- Microsoft.Azure.Cosmos.sln | 16 +- .../src/Resource/Container/Container.cs | 28 +- .../Resource/Container/ContainerCore.Items.cs | 63 +---- .../Resource/Container/ContainerInlineCore.cs | 10 +- .../Resource/Container/ContainerInternal.cs | 4 - .../CosmosContainerTests.cs | 97 +++---- .../Contracts/DotNetPreviewSDKAPI.json | 20 +- .../FindOverlappingRangesTests.cs | 240 ------------------ 8 files changed, 104 insertions(+), 374 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs diff --git a/Microsoft.Azure.Cosmos.sln b/Microsoft.Azure.Cosmos.sln index d412905195..03a17e2f39 100644 --- a/Microsoft.Azure.Cosmos.sln +++ b/Microsoft.Azure.Cosmos.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29123.88 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35201.131 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos", "Microsoft.Azure.Cosmos\src\Microsoft.Azure.Cosmos.csproj", "{36F6F6A8-CEC8-4261-9948-903495BC3C25}" EndProject @@ -164,6 +164,18 @@ Global {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|Any CPU.Build.0 = Release|Any CPU {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|x64.ActiveCfg = Release|Any CPU {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|x64.Build.0 = Release|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|Any CPU.Build.0 = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|x64.ActiveCfg = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|x64.Build.0 = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|x64.Build.0 = Debug|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.Build.0 = Release|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.ActiveCfg = Release|Any CPU + {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 2445ef2d96..5aece0e63b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1755,23 +1755,29 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); - + /// - /// Takes a given list of ranges and find overlapping ranges for the given partition key. + /// Takes a given parent feed range and a partition key and checks if the partition key is a subset of the parent feed range. /// - /// A given partition key. - /// A given list of ranges. + /// A given partition key. + /// A given list of ranges. /// - /// A list of overlapping ranges for the the given partition key. - public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); + /// True or False + public virtual async Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw await Task.FromException(default); + } /// - /// Takes a given list of ranges and find overlapping ranges for the given feed range. + /// Takes 2 given feed ranges representing a parent and child feed range and checks if the child feed range is a subset of the parent feed range. /// - /// A given feed range. - /// A given list of ranges. - /// A list of overlapping ranges for the the given feed range epk. - public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); + /// A feed range that represents a parent range. + /// A feed range tha represents a child range. + /// True or False + public virtual bool IsSubset(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange) + { + throw new NotImplementedException(); + } #endif } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 39516ab357..953de3f310 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1254,54 +1254,27 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } - public override async Task> FindOverlappingRangesAsync( - Cosmos.PartitionKey partitionKey, - IReadOnlyList feedRanges, - CancellationToken cancellationToken = default) +#if PREVIEW + public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRange) { - List overlappingRanges = new (); - - Documents.Routing.Range rangeFromPartitionKey = await this.ConvertToRangeAsync( - partitionKey: partitionKey, - cancellationToken: cancellationToken); - - foreach (Documents.Routing.Range range in ContainerCore.ConvertToRange(feedRanges)) - { - if (Documents.Routing.Range.CheckOverlapping( - range1: rangeFromPartitionKey, - range2: range)) - { - overlappingRanges.Add(new FeedRangeEpk(range)); - } - } - - return overlappingRanges; + return Documents.Routing.Range.CheckOverlapping( + range1: ContainerCore.ConvertToRange(parentFeedRange), + range2: ContainerCore.ConvertToRange(childFeedRange)); } - public override IReadOnlyList FindOverlappingRanges( - Cosmos.FeedRange feedRange, - IReadOnlyList feedRanges) + public override async Task IsSubsetAsync(FeedRange parentFeedRange, PartitionKey partitionKey, CancellationToken cancellationToken = default) { - List overlappingRanges = new (); - - foreach (Documents.Routing.Range range in ContainerCore.ConvertToRange(feedRanges)) - { - if (Documents.Routing.Range.CheckOverlapping( - range1: ContainerCore.ConvertToRange(feedRange), - range2: range)) - { - overlappingRanges.Add(new FeedRangeEpk(range)); - } - } - - return overlappingRanges; + return Documents.Routing.Range.CheckOverlapping( + range1: ContainerCore.ConvertToRange(parentFeedRange), + range2: await this.ConvertToRangeAsync( + partitionKey: partitionKey, + cancellationToken: cancellationToken)); } - +#endif private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) { PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - Documents.Routing.Range range = Documents.Routing.Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); - return range; + return Documents.Routing.Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); } private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) @@ -1312,14 +1285,6 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( } } - private static Documents.Routing.Range ConvertToRange(FeedRange fromFeedRange) - { - if (fromFeedRange is not FeedRangeEpk feedRangeEpk) - { - return default; - } - - return feedRangeEpk.Range; - } + private static Documents.Routing.Range ConvertToRange(FeedRange fromFeedRange) => ((FeedRangeEpk)fromFeedRange).Range; } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index b9a8854a6d..5d3f7163f7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -661,14 +661,16 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( openTelemetry: (response) => new OpenTelemetryResponse(response)); } - public override IReadOnlyList FindOverlappingRanges(FeedRange feedRange, IReadOnlyList feedRanges) +#if PREVIEW + public override Task IsSubsetAsync(FeedRange parentFeedRange, PartitionKey partitionKey, CancellationToken cancellationToken = default) { - return base.FindOverlappingRanges(feedRange, feedRanges); + return base.IsSubsetAsync(parentFeedRange, partitionKey, cancellationToken); } - public override async Task> FindOverlappingRangesAsync(PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default) + public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRange) { - return await base.FindOverlappingRangesAsync(partitionKey, feedRanges, cancellationToken); + return base.IsSubset(parentFeedRange, childFeedRange); } +#endif } } \ 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 7ee6239bef..cedef3f3de 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -151,10 +151,6 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); - - public abstract Task> FindOverlappingRangesAsync(Cosmos.PartitionKey partitionKey, IReadOnlyList feedRanges, CancellationToken cancellationToken = default); - - public abstract IReadOnlyList FindOverlappingRanges(Cosmos.FeedRange feedRange, IReadOnlyList feedRanges); #endif public abstract class TryExecuteQueryResult diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index f44c2b4197..f26c7b0703 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -630,7 +629,7 @@ public async Task CreateContainerIfNotExistsAsyncTest() requestChargeHandler.TotalRequestCharges = 0; ContainerResponse createWithConflictResponse = await database.CreateContainerIfNotExistsAsync( - Guid.NewGuid().ToString(), + Guid.NewGuid().ToString(), "/pk"); Assert.AreEqual(requestChargeHandler.TotalRequestCharges, createWithConflictResponse.RequestCharge); @@ -750,9 +749,9 @@ public async Task CreateContainerIfNotExistsAsyncForMultiHashCollectionsTest() Assert.AreEqual(nameof(settings.PartitionKey), ex.ParamName); Assert.IsTrue(ex.Message.Contains(string.Format( ClientResources.PartitionKeyPathConflict, - string.Join(",",partitionKeyPath2), + string.Join(",", partitionKeyPath2), containerName, - string.Join(",",partitionKeyPath1)))); + string.Join(",", partitionKeyPath1)))); } // Mismatch in the 2nd path @@ -773,10 +772,10 @@ public async Task CreateContainerIfNotExistsAsyncForMultiHashCollectionsTest() string.Join(",", partitionKeyPath3), containerName, string.Join(",", partitionKeyPath1)))); - } - - - //Create and fetch container with same paths + } + + + //Create and fetch container with same paths List partitionKeyPath4 = new List(); partitionKeyPath4.Add("/users"); partitionKeyPath4.Add("/sessionId"); @@ -1395,7 +1394,7 @@ public async Task ClientEncryptionPolicyTest() { Id = containerName, PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash }, - ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths:paths,policyFormatVersion:2) + ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths: paths, policyFormatVersion: 2) }; ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(setting); @@ -1424,9 +1423,9 @@ public async Task ClientEncryptionPolicyTest() ContainerResponse readResponse = await container.ReadContainerAsync(); Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); - Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); - - // version 1 test. + Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); + + // version 1 test. containerName = Guid.NewGuid().ToString(); partitionKeyPath = "/users"; paths = new Collection() @@ -1474,9 +1473,9 @@ public async Task ClientEncryptionPolicyTest() readResponse = await container.ReadContainerAsync(); Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); - Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); - - // replace without updating CEP should be successful + Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); + + // replace without updating CEP should be successful readResponse.Resource.IndexingPolicy = new Cosmos.IndexingPolicy() { IndexingMode = Cosmos.IndexingMode.None, @@ -1529,7 +1528,7 @@ public async Task ClientEncryptionPolicyFailureTest() }; Assert.Fail("Creating ContainerProperties should have failed."); - } + } catch (ArgumentException ex) { Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'."), ex.Message); @@ -1562,7 +1561,7 @@ public async Task ClientEncryptionPolicyFailureTest() { Id = containerName, PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash }, - ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsList) + ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsList) }; Assert.Fail("Creating ContainerProperties should have failed."); @@ -1787,13 +1786,13 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container #if PREVIEW [TestMethod] [Owner("philipthomas-MSFT")] - public async Task TestFindOverlappingRangesByPartitionKeyAsync() + public async Task GivenParentFeedRangeAndPartitionKeyIsSubsetTestAsync() { Container container = default; try { - PartitionKey partitionKey = new PartitionKey("GA"); + PartitionKey partitionKey = new ("WA"); ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk"); @@ -1802,24 +1801,12 @@ public async Task TestFindOverlappingRangesByPartitionKeyAsync() await container.CreateItemAsync(item: new { id = Guid.NewGuid().ToString(), pk = "GA" }, partitionKey: partitionKey); - Documents.Routing.Range range = new Documents.Routing.Range("AA", "BB", true, false); + Documents.Routing.Range range = new Documents.Routing.Range("", "AA", true, false); FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); - string representation = feedRangeEpk.ToJsonString(); - List ranges = new List() - { - FeedRange.FromJsonString(representation), - }; + bool actualIsSubset = await container.IsSubsetAsync(parentFeedRange: feedRangeEpk, partitionKey: partitionKey); - IReadOnlyList results = await container.FindOverlappingRangesAsync( - partitionKey: partitionKey, - feedRanges: ranges); - - Assert.IsNotNull(results); - } - catch (Exception ex) - { - Logger.LogLine($"{ex}"); + Assert.IsTrue(actualIsSubset); } finally { @@ -1832,39 +1819,39 @@ public async Task TestFindOverlappingRangesByPartitionKeyAsync() [TestMethod] [Owner("philipthomas-MSFT")] - public async Task TestFindOverlappingRangesByFeedRangeAsync() + [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] + [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] + [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] + [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] + [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] + [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] + [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] + [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] + [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] + public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( + string childMin, + string childMax, + string parentMin, + string parentMax, + bool expectedIsSubset) { Container container = default; try { - PartitionKey partitionKey = new PartitionKey("GA"); + PartitionKey partitionKey = new("WA"); ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk"); container = containerResponse.Container; - await container.CreateItemAsync(item: new { id = Guid.NewGuid().ToString(), pk = "GA" }, partitionKey: partitionKey); - - Documents.Routing.Range range = new Documents.Routing.Range("AA", "BB", true, false); - FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); - string representation = feedRangeEpk.ToJsonString(); + bool actualIsSubset = container.IsSubset( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, true, false))); - List ranges = new List() - { - FeedRange.FromJsonString(representation), - }; - - IReadOnlyList results = container.FindOverlappingRanges( - feedRange: FeedRange.FromJsonString(FeedRangeEpk.FullRange.ToJsonString()), - feedRanges: ranges); - - Assert.IsNotNull(results); - } - catch (Exception ex) - { - Logger.LogLine($"{ex}"); + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } finally { 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 c5e27b0ed7..dd2b5b3f62 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 @@ -299,30 +299,32 @@ "Microsoft.Azure.Cosmos.Container;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1)": { + "Boolean IsSubset(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange)": { "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Boolean IsSubset(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange])": { + "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange] FindOverlappingRanges(Microsoft.Azure.Cosmos.FeedRange, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { + "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.PartitionKey, System.Threading.CancellationToken)[System.Runtime.CompilerServices.AsyncStateMachineAttribute(typeof(Microsoft.Azure.Cosmos.Container+))]": { "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Attributes": [ + "AsyncStateMachineAttribute" + ], + "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.PartitionKey, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken)": { + "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange]] FindOverlappingRangesAsync(Microsoft.Azure.Cosmos.PartitionKey, System.Collections.Generic.IReadOnlyList`1[Microsoft.Azure.Cosmos.FeedRange], System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs deleted file mode 100644 index 4daa099167..0000000000 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FindOverlappingRangesTests.cs +++ /dev/null @@ -1,240 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Telemetry; - using Microsoft.Azure.Cosmos.Tracing; - using Microsoft.Azure.Documents; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - using Newtonsoft.Json; - - [TestClass] - public class FindOverlappingRangesTests - { - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("When a given feed range has x overlaps for y feed ranges.")] - [DataRow(1, 0)] - [DataRow(2, 0)] - [DataRow(2, 1)] - [DataRow(3, 0)] - [DataRow(3, 1)] - [DataRow(3, 2)] - [DataRow(10, 0)] - [DataRow(10, 1)] - [DataRow(10, 2)] - [DataRow(10, 3)] - [DataRow(10, 4)] - [DataRow(10, 5)] - [DataRow(10, 6)] - [DataRow(10, 7)] - [DataRow(10, 8)] - [DataRow(10, 9)] - public void GivenAFeedRangeAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTest( - int numberOfRanges, - int expectedOverlap) - { - ContainerInternal container = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient() - .GetContainer( - databaseId: Guid.NewGuid().ToString(), - containerId: Guid.NewGuid().ToString()); - - IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( - minHexValue: "", - maxHexValue: "FF", - numberOfRanges: numberOfRanges); - - Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRangeThatOverlap( - overlap: expectedOverlap, - feedRanges: feedRanges.ToList()); - - Logger.LogLine($"{feedRange} -> {feedRange.ToJsonString()}"); - - IReadOnlyList overlappingRanges = container.FindOverlappingRanges( - feedRange: feedRange, - feedRanges: feedRanges.ToList()); - - Assert.IsNotNull(overlappingRanges); - Logger.LogLine($"{nameof(overlappingRanges)} -> {JsonConvert.SerializeObject(overlappingRanges)}"); - - int actualOverlap = overlappingRanges.Count; - - Assert.AreEqual( - expected: expectedOverlap + 1, - actual: actualOverlap, - message: $"The given feedRange should have {expectedOverlap + 1} overlaps for {numberOfRanges} feedRanges."); - - Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0))); - Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0 + expectedOverlap))); - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Given a partition key and a X number of ranges, When checking to find which range the partition key belongs to, Then the" + - " range is found at id Y.")] - [DataRow(1, 1)] - [DataRow(2, 1)] - [DataRow(10, 1)] - public async Task GivenAPartitionKeyAndYFeedRangesWhenTheFeedRangeOverlapsThenReturnXOverlappingRangesTestAsync( - int numberOfRanges, - int expectedRangeId) - { - ITrace trace = Trace.GetRootTrace("TestTrace"); - - IEnumerable feedRanges = FindOverlappingRangesTests.CreateFeedRanges( - minHexValue: "", - maxHexValue: "FF", - numberOfRanges: numberOfRanges); - - List> partitionKeyRanges = new (feedRanges.Count()); - - for (int counter = 0; counter < feedRanges.Count(); counter++) - { - Documents.Routing.Range range = ((FeedRangeEpk)feedRanges.ElementAt(counter)).Range; - - partitionKeyRanges.Add( - new Tuple( - new PartitionKeyRange - { - Id = counter.ToString(), - MinInclusive = range.Min, - MaxExclusive = range.Max - }, - (ServiceIdentity)null)); - } - - string containerId = Guid.NewGuid().ToString(); - ContainerInternal container = (ContainerInternal)MockCosmosUtil - .CreateMockCosmosClient() - .GetContainer( - databaseId: Guid.NewGuid().ToString(), - containerId: containerId); - - PartitionKeyDefinition partitionKeyDefinition = new(); - partitionKeyDefinition.Paths.Add("/pk"); - partitionKeyDefinition.Version = PartitionKeyDefinitionVersion.V1; - Cosmos.PartitionKey partitionKey = new Cosmos.PartitionKey("test"); - - const string collectionRid = "DvZRAOvLgDM="; - ContainerProperties containerProperties = ContainerProperties.CreateWithResourceId(collectionRid); - containerProperties.Id = containerId; - containerProperties.PartitionKey = partitionKeyDefinition; - - IReadOnlyList overlappingRanges = await container.FindOverlappingRangesAsync( - partitionKey: partitionKey, - feedRanges: feedRanges.ToList()); - - Assert.IsNotNull(overlappingRanges); - Logger.LogLine($"{nameof(overlappingRanges)} -> {JsonConvert.SerializeObject(overlappingRanges)}"); - - int actualOverlap = overlappingRanges.Count; - - Assert.AreEqual( - expected: expectedRangeId, - actual: actualOverlap, - message: $"The given partition key should be at range {expectedRangeId}."); - - Assert.IsTrue(overlappingRanges.Contains(feedRanges.ElementAt(0))); - } - - private Mock MockClientContext() - { - Mock mockContext = new Mock(); - mockContext.Setup(x => x.OperationHelperAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny>>(), - It.IsAny>(), - It.IsAny(), - It.IsAny())) - .Returns>, Func, TraceComponent, Microsoft.Azure.Cosmos.Tracing.TraceLevel>( - (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, comp, level) => func(NoOpTrace.Singleton)); - - mockContext.Setup(x => x.Client).Returns(MockCosmosUtil.CreateMockCosmosClient()); - - return mockContext; - } - - private static Cosmos.FeedRange CreateFeedRangeThatOverlap(int overlap, IReadOnlyList feedRanges) - { - string min = ((FeedRangeEpk)feedRanges[0]).Range.Min; - string max = ((FeedRangeEpk)feedRanges[0 + overlap]).Range.Max; - - Cosmos.FeedRange feedRange = FindOverlappingRangesTests.CreateFeedRange( - min: min, - max: max); - - return feedRange; - } - - private static Cosmos.FeedRange CreateFeedRange(string min, string max) - { - if (min == "0") - { - min = ""; - } - - Documents.Routing.Range range = new ( - min: min, - max: max, - isMinInclusive: true, - isMaxInclusive: false); - - FeedRangeEpk feedRangeEpk = new (range); - - return Cosmos.FeedRange.FromJsonString(feedRangeEpk.ToJsonString()); - } - - private static IEnumerable CreateFeedRanges( - string minHexValue, - string maxHexValue, - int numberOfRanges = 10) - { - if (minHexValue == string.Empty) - { - minHexValue = "0"; - } - - // Convert hex strings to ulong - ulong minValue = ulong.Parse(minHexValue, System.Globalization.NumberStyles.HexNumber); - ulong maxValue = ulong.Parse(maxHexValue, System.Globalization.NumberStyles.HexNumber); - - ulong range = maxValue - minValue + 1; // Include the upper boundary - ulong stepSize = range / (ulong)numberOfRanges; - - // Generate the sub-ranges - List<(string, string)> subRanges = new (); - ulong splitMaxValue = default; - - for (int i = 0; i < numberOfRanges; i++) - { - ulong splitMinValue = splitMaxValue; - splitMaxValue = (i == numberOfRanges - 1) ? maxValue : splitMinValue + stepSize - 1; - subRanges.Add((splitMinValue.ToString("X"), splitMaxValue.ToString("X"))); - } - - List rs = new List(); - - foreach ((string min, string max) in subRanges) - { - Logger.LogLine($"{min} - {max}"); - - rs.Add(FindOverlappingRangesTests.CreateFeedRange( - min: min, - max: max)); - } - - return rs; - } - } -} From 64ba76440b9b18d3ba2f1cfd7320001349169f87 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 13:46:29 -0400 Subject: [PATCH 040/145] removed from Encryption --- .../src/EncryptionContainer.cs | 17 -------------- .../src/EncryptionContainer.cs | 23 ------------------- 2 files changed, 40 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 497d2cf209..2b1873f8e4 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1029,23 +1029,6 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV } #endif -#if SDKPROJECTREF - public override Task> FindOverlappingRangesAsync( - Cosmos.PartitionKey partitionKey, - IReadOnlyList feedRanges, - CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public override IReadOnlyList FindOverlappingRanges( - Cosmos.FeedRange feedRange, - IReadOnlyList feedRanges) - { - throw new NotImplementedException(); - } -#endif - private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, ReadManyRequestOptions readManyRequestOptions = null, diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index 5317a04797..a4dca5060b 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -755,29 +755,6 @@ public override Task> GetPartitionKeyRangesAsync( } #endif -#if SDKPROJECTREF - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - string processorName, - ChangeFeedHandler> onChangesDelegate) - { - throw new NotImplementedException(); - } - - public override Task> FindOverlappingRangesAsync( - Cosmos.PartitionKey partitionKey, - IReadOnlyList feedRanges, - CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public override IReadOnlyList FindOverlappingRanges( - Cosmos.FeedRange feedRange, - IReadOnlyList feedRanges) - { - throw new NotImplementedException(); - } -#endif /// /// This function handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy. /// The idea is to have the container Rid cached and sent out as part of RequestOptions with Container Rid set in "x-ms-cosmos-intended-collection-rid" header. From f5fc5bb7ab45da9163d3fecc17ced3189e40e57a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 13:52:34 -0400 Subject: [PATCH 041/145] add back GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes --- .../src/EncryptionContainer.cs | 8 +++ ...ndexUtilizationClientSideExistenceTest.xml | 64 +++++++++---------- ...eTest.IndexUtilizationHeaderLengthTest.xml | 8 +-- ...rserBaselineTest.IndexUtilizationParse.xml | 22 +++---- 4 files changed, 55 insertions(+), 47 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index a4dca5060b..64c7dc36a1 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -755,6 +755,14 @@ public override Task> GetPartitionKeyRangesAsync( } #endif +#if SDKPROJECTREF + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + string processorName, + ChangeFeedHandler> onChangesDelegate) + { + throw new NotImplementedException(); + } +#endif /// /// This function handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy. /// The idea is to have the container Rid cached and sent out as part of RequestOptions with Container Rid set in "x-ms-cosmos-intended-collection-rid" header. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml index 19ee94a35b..4f12c7312b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ - + @@ -23,7 +23,7 @@ "Abc"]]> - + @@ -32,7 +32,7 @@ "1" and c.name > "Abc"]]> - + @@ -41,7 +41,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -59,7 +59,7 @@ "Abc"]]> - + @@ -68,7 +68,7 @@ "1" and c.name > "Abc"]]> - + @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -95,7 +95,7 @@ "Abc"]]> - + @@ -104,7 +104,7 @@ "1" and c.name > "Abc"]]> - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -131,7 +131,7 @@ "Abc" ORDER BY c.id ASC]]> - + @@ -140,7 +140,7 @@ "1" and c.name > "Abc" ORDER BY c.id ASC]]> - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -167,7 +167,7 @@ "Abc" GROUP BY c.id]]> - + @@ -176,7 +176,7 @@ "1" and c.name > "Abc" GROUP BY c.id]]> - + @@ -185,7 +185,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -203,7 +203,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -212,7 +212,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + @@ -221,7 +221,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -239,7 +239,7 @@ "Abc"]]> - + @@ -248,7 +248,7 @@ "1" and c.name > "Abc"]]> - + @@ -257,7 +257,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -275,7 +275,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -284,7 +284,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml index 4e2d3b1ade..90bb023e23 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ 0 AND r.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb > 0]]> - + @@ -23,7 +23,7 @@ 0]]> - + @@ -32,7 +32,7 @@ 0 AND r.cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc > 0]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml index c1d041160a..414ce6838f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml @@ -7,7 +7,7 @@ FROM c WHERE STARTSWITH(c.statement, 'The quick brown fox jumps over the lazy dog', false)]]> - + @@ -18,7 +18,7 @@ FROM c WHERE STARTSWITH(c['Η γρήγορη καφέ αλεπού πηδάει πάνω από το τεμπέλικο ÏƑκυλί'], 's', false)]]> - + @@ -27,7 +27,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -36,7 +36,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -45,7 +45,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ?:"{}|ßÁŒÆ12ếàưỏốởặ'], 's', true) FROM root r]]> - ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?"}],"CompositeIndexes":[]},"PotentialIndexes":{"SingleIndexes":[],"CompositeIndexes":[]}}]]> + ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?","FilterPreciseSet":true,"IndexPreciseSet":true,"IndexImpactScore":"High"}],"PotentialSingleIndexes":[],"UtilizedCompositeIndexes":[],"PotentialCompositeIndexes":[]}]]> @@ -54,7 +54,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -63,7 +63,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -72,7 +72,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -81,7 +81,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -90,7 +90,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -99,7 +99,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + \ No newline at end of file From 5fe347f462418c947b02818606e569ec82e8d05c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 13:54:56 -0400 Subject: [PATCH 042/145] reverting back to origin --- ...ndexUtilizationClientSideExistenceTest.xml | 64 +++++++++---------- ...eTest.IndexUtilizationHeaderLengthTest.xml | 8 +-- ...rserBaselineTest.IndexUtilizationParse.xml | 22 +++---- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml index 4f12c7312b..19ee94a35b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ - + @@ -23,7 +23,7 @@ "Abc"]]> - + @@ -32,7 +32,7 @@ "1" and c.name > "Abc"]]> - + @@ -41,7 +41,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -59,7 +59,7 @@ "Abc"]]> - + @@ -68,7 +68,7 @@ "1" and c.name > "Abc"]]> - + @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -95,7 +95,7 @@ "Abc"]]> - + @@ -104,7 +104,7 @@ "1" and c.name > "Abc"]]> - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -131,7 +131,7 @@ "Abc" ORDER BY c.id ASC]]> - + @@ -140,7 +140,7 @@ "1" and c.name > "Abc" ORDER BY c.id ASC]]> - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -167,7 +167,7 @@ "Abc" GROUP BY c.id]]> - + @@ -176,7 +176,7 @@ "1" and c.name > "Abc" GROUP BY c.id]]> - + @@ -185,7 +185,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -203,7 +203,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -212,7 +212,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + @@ -221,7 +221,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -239,7 +239,7 @@ "Abc"]]> - + @@ -248,7 +248,7 @@ "1" and c.name > "Abc"]]> - + @@ -257,7 +257,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -275,7 +275,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -284,7 +284,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml index 90bb023e23..4e2d3b1ade 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ 0 AND r.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb > 0]]> - + @@ -23,7 +23,7 @@ 0]]> - + @@ -32,7 +32,7 @@ 0 AND r.cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc > 0]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml index 414ce6838f..c1d041160a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml @@ -7,7 +7,7 @@ FROM c WHERE STARTSWITH(c.statement, 'The quick brown fox jumps over the lazy dog', false)]]> - + @@ -18,7 +18,7 @@ FROM c WHERE STARTSWITH(c['Η γρήγορη καφέ αλεπού πηδάει πάνω από το τεμπέλικο ÏƑκυλί'], 's', false)]]> - + @@ -27,7 +27,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -36,7 +36,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -45,7 +45,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ?:"{}|ßÁŒÆ12ếàưỏốởặ'], 's', true) FROM root r]]> - ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?","FilterPreciseSet":true,"IndexPreciseSet":true,"IndexImpactScore":"High"}],"PotentialSingleIndexes":[],"UtilizedCompositeIndexes":[],"PotentialCompositeIndexes":[]}]]> + ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?"}],"CompositeIndexes":[]},"PotentialIndexes":{"SingleIndexes":[],"CompositeIndexes":[]}}]]> @@ -54,7 +54,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -63,7 +63,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -72,7 +72,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -81,7 +81,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -90,7 +90,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -99,7 +99,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + \ No newline at end of file From df9fecddf2080f40c55b958c95168187512648f4 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 14:02:27 -0400 Subject: [PATCH 043/145] NotImplementedException>() fix on container --- .../src/Resource/Container/Container.cs | 4 +- ...ndexUtilizationClientSideExistenceTest.xml | 64 +++++++++---------- ...eTest.IndexUtilizationHeaderLengthTest.xml | 8 +-- ...rserBaselineTest.IndexUtilizationParse.xml | 22 +++---- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 5aece0e63b..1ad79b62a8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1763,9 +1763,9 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// A given list of ranges. /// /// True or False - public virtual async Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.PartitionKey partitionKey, CancellationToken cancellationToken = default) + public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.PartitionKey partitionKey, CancellationToken cancellationToken = default) { - throw await Task.FromException(default); + throw NotImplementedException>(); } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml index 19ee94a35b..4f12c7312b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ - + @@ -23,7 +23,7 @@ "Abc"]]> - + @@ -32,7 +32,7 @@ "1" and c.name > "Abc"]]> - + @@ -41,7 +41,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -59,7 +59,7 @@ "Abc"]]> - + @@ -68,7 +68,7 @@ "1" and c.name > "Abc"]]> - + @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -95,7 +95,7 @@ "Abc"]]> - + @@ -104,7 +104,7 @@ "1" and c.name > "Abc"]]> - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -131,7 +131,7 @@ "Abc" ORDER BY c.id ASC]]> - + @@ -140,7 +140,7 @@ "1" and c.name > "Abc" ORDER BY c.id ASC]]> - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -167,7 +167,7 @@ "Abc" GROUP BY c.id]]> - + @@ -176,7 +176,7 @@ "1" and c.name > "Abc" GROUP BY c.id]]> - + @@ -185,7 +185,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -203,7 +203,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -212,7 +212,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + @@ -221,7 +221,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -239,7 +239,7 @@ "Abc"]]> - + @@ -248,7 +248,7 @@ "1" and c.name > "Abc"]]> - + @@ -257,7 +257,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -275,7 +275,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -284,7 +284,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml index 4e2d3b1ade..90bb023e23 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ 0 AND r.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb > 0]]> - + @@ -23,7 +23,7 @@ 0]]> - + @@ -32,7 +32,7 @@ 0 AND r.cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc > 0]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml index c1d041160a..414ce6838f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml @@ -7,7 +7,7 @@ FROM c WHERE STARTSWITH(c.statement, 'The quick brown fox jumps over the lazy dog', false)]]> - + @@ -18,7 +18,7 @@ FROM c WHERE STARTSWITH(c['Η γρήγορη καφέ αλεπού πηδάει πάνω από το τεμπέλικο ÏƑκυλί'], 's', false)]]> - + @@ -27,7 +27,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -36,7 +36,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -45,7 +45,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ?:"{}|ßÁŒÆ12ếàưỏốởặ'], 's', true) FROM root r]]> - ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?"}],"CompositeIndexes":[]},"PotentialIndexes":{"SingleIndexes":[],"CompositeIndexes":[]}}]]> + ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?","FilterPreciseSet":true,"IndexPreciseSet":true,"IndexImpactScore":"High"}],"PotentialSingleIndexes":[],"UtilizedCompositeIndexes":[],"PotentialCompositeIndexes":[]}]]> @@ -54,7 +54,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -63,7 +63,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -72,7 +72,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -81,7 +81,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -90,7 +90,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -99,7 +99,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + \ No newline at end of file From 78618cdf8162b60fae63683877d507bf3c3c16f5 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 14:12:50 -0400 Subject: [PATCH 044/145] reverting sln --- Microsoft.Azure.Cosmos.sln | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos.sln b/Microsoft.Azure.Cosmos.sln index 03a17e2f39..d412905195 100644 --- a/Microsoft.Azure.Cosmos.sln +++ b/Microsoft.Azure.Cosmos.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.35201.131 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos", "Microsoft.Azure.Cosmos\src\Microsoft.Azure.Cosmos.csproj", "{36F6F6A8-CEC8-4261-9948-903495BC3C25}" EndProject @@ -164,18 +164,6 @@ Global {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|Any CPU.Build.0 = Release|Any CPU {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|x64.ActiveCfg = Release|Any CPU {B5B3631D-AC2F-4257-855D-D6FE12F20B60}.Release|x64.Build.0 = Release|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|Any CPU.ActiveCfg = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|Any CPU.Build.0 = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|x64.ActiveCfg = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Cover|x64.Build.0 = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|x64.ActiveCfg = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Debug|x64.Build.0 = Debug|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.Build.0 = Release|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.ActiveCfg = Release|Any CPU - {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 1378a550854c33e3fbfb0869a48121955a2ad989 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 14 Aug 2024 14:14:39 -0400 Subject: [PATCH 045/145] hmm. these baseline files. --- ...ndexUtilizationClientSideExistenceTest.xml | 64 +++++++++---------- ...eTest.IndexUtilizationHeaderLengthTest.xml | 8 +-- ...rserBaselineTest.IndexUtilizationParse.xml | 22 +++---- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml index 4f12c7312b..19ee94a35b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationClientSideExistenceTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ - + @@ -23,7 +23,7 @@ "Abc"]]> - + @@ -32,7 +32,7 @@ "1" and c.name > "Abc"]]> - + @@ -41,7 +41,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -59,7 +59,7 @@ "Abc"]]> - + @@ -68,7 +68,7 @@ "1" and c.name > "Abc"]]> - + @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -95,7 +95,7 @@ "Abc"]]> - + @@ -104,7 +104,7 @@ "1" and c.name > "Abc"]]> - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -131,7 +131,7 @@ "Abc" ORDER BY c.id ASC]]> - + @@ -140,7 +140,7 @@ "1" and c.name > "Abc" ORDER BY c.id ASC]]> - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -167,7 +167,7 @@ "Abc" GROUP BY c.id]]> - + @@ -176,7 +176,7 @@ "1" and c.name > "Abc" GROUP BY c.id]]> - + @@ -185,7 +185,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -203,7 +203,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -212,7 +212,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + @@ -221,7 +221,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -239,7 +239,7 @@ "Abc"]]> - + @@ -248,7 +248,7 @@ "1" and c.name > "Abc"]]> - + @@ -257,7 +257,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -275,7 +275,7 @@ "Abc" GROUP BY c.id, c.name]]> - + @@ -284,7 +284,7 @@ "1" and c.name > "Abc" GROUP BY c.id, c.name]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml index 90bb023e23..4e2d3b1ade 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationHeaderLengthTest.xml @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ 0 AND r.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb > 0]]> - + @@ -23,7 +23,7 @@ 0]]> - + @@ -32,7 +32,7 @@ 0 AND r.cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc > 0]]> - + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml index 414ce6838f..c1d041160a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/IndexMetricsParserBaselineTest.IndexUtilizationParse.xml @@ -7,7 +7,7 @@ FROM c WHERE STARTSWITH(c.statement, 'The quick brown fox jumps over the lazy dog', false)]]> - + @@ -18,7 +18,7 @@ FROM c WHERE STARTSWITH(c['Η γρήγορη καφέ αλεπού πηδάει πάνω από το τεμπέλικο ÏƑκυλί'], 's', false)]]> - + @@ -27,7 +27,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -36,7 +36,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -45,7 +45,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ?:"{}|ßÁŒÆ12ếàưỏốởặ'], 's', true) FROM root r]]> - ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?","FilterPreciseSet":true,"IndexPreciseSet":true,"IndexImpactScore":"High"}],"PotentialSingleIndexes":[],"UtilizedCompositeIndexes":[],"PotentialCompositeIndexes":[]}]]> + ?:\\\"{}|ßÁŒÆ12ếàưỏốởặ\"\/?"}],"CompositeIndexes":[]},"PotentialIndexes":{"SingleIndexes":[],"CompositeIndexes":[]}}]]> @@ -54,7 +54,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -63,7 +63,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -72,7 +72,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -81,7 +81,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -90,7 +90,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + @@ -99,7 +99,7 @@ WHERE STARTSWITH(c['Η γρήγορη καφέ αΠ- + \ No newline at end of file From a9d59a245f3329ef936395807a895359be05cdd4 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 15 Aug 2024 09:15:38 -0400 Subject: [PATCH 046/145] revwer to origin --- .../src/EncryptionContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 2b1873f8e4..b898a77179 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1028,7 +1028,6 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV onChangesDelegate); } #endif - private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, ReadManyRequestOptions readManyRequestOptions = null, From b45f34b217a534a8581b37db4598d1196ee7234d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 15 Aug 2024 09:27:27 -0400 Subject: [PATCH 047/145] removed api for PartitionKey since user can call FeedRange.FromPartitionKey() with the one method API that takes FeedRange. --- .../src/Resource/Container/Container.cs | 12 ------- .../Resource/Container/ContainerCore.Items.cs | 14 -------- .../Resource/Container/ContainerInlineCore.cs | 5 --- .../CosmosContainerTests.cs | 33 ------------------- .../Contracts/DotNetPreviewSDKAPI.json | 7 ---- 5 files changed, 71 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 1ad79b62a8..2fa183993d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1756,18 +1756,6 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV string processorName, ChangeFeedHandler> onChangesDelegate); - /// - /// Takes a given parent feed range and a partition key and checks if the partition key is a subset of the parent feed range. - /// - /// A given partition key. - /// A given list of ranges. - /// - /// True or False - public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.PartitionKey partitionKey, CancellationToken cancellationToken = default) - { - throw NotImplementedException>(); - } - /// /// Takes 2 given feed ranges representing a parent and child feed range and checks if the child feed range is a subset of the parent feed range. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 953de3f310..08f10bc126 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1261,21 +1261,7 @@ public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRang range1: ContainerCore.ConvertToRange(parentFeedRange), range2: ContainerCore.ConvertToRange(childFeedRange)); } - - public override async Task IsSubsetAsync(FeedRange parentFeedRange, PartitionKey partitionKey, CancellationToken cancellationToken = default) - { - return Documents.Routing.Range.CheckOverlapping( - range1: ContainerCore.ConvertToRange(parentFeedRange), - range2: await this.ConvertToRangeAsync( - partitionKey: partitionKey, - cancellationToken: cancellationToken)); - } #endif - private async Task> ConvertToRangeAsync(PartitionKey partitionKey, CancellationToken cancellationToken) - { - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - return Documents.Routing.Range.GetPointRange(partitionKey.InternalKey.GetEffectivePartitionKeyString(partitionKeyDefinition)); - } private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) { diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 5d3f7163f7..888d19fddc 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -662,11 +662,6 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } #if PREVIEW - public override Task IsSubsetAsync(FeedRange parentFeedRange, PartitionKey partitionKey, CancellationToken cancellationToken = default) - { - return base.IsSubsetAsync(parentFeedRange, partitionKey, cancellationToken); - } - public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRange) { return base.IsSubset(parentFeedRange, childFeedRange); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index f26c7b0703..b534fe1e0f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1784,39 +1784,6 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container } #if PREVIEW - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenParentFeedRangeAndPartitionKeyIsSubsetTestAsync() - { - Container container = default; - - try - { - PartitionKey partitionKey = new ("WA"); - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - await container.CreateItemAsync(item: new { id = Guid.NewGuid().ToString(), pk = "GA" }, partitionKey: partitionKey); - - Documents.Routing.Range range = new Documents.Routing.Range("", "AA", true, false); - FeedRangeEpk feedRangeEpk = new FeedRangeEpk(range); - - bool actualIsSubset = await container.IsSubsetAsync(parentFeedRange: feedRangeEpk, partitionKey: partitionKey); - - Assert.IsTrue(actualIsSubset); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] 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 dd2b5b3f62..c82d2d6c40 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 @@ -314,13 +314,6 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.PartitionKey, System.Threading.CancellationToken)[System.Runtime.CompilerServices.AsyncStateMachineAttribute(typeof(Microsoft.Azure.Cosmos.Container+))]": { - "Type": "Method", - "Attributes": [ - "AsyncStateMachineAttribute" - ], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.PartitionKey, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], From fb0566394e9e96bf0e207c8d0a519ce63caed368 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 15 Aug 2024 10:55:53 -0400 Subject: [PATCH 048/145] reworked IsSubsetAsync based on suggestions to use FeedRangeInteranl to deal with hierarchical partiton keys. --- .../src/Resource/Container/Container.cs | 3 +- .../Resource/Container/ContainerCore.Items.cs | 42 +++++--- .../Resource/Container/ContainerInlineCore.cs | 4 +- .../CosmosContainerTests.cs | 101 +++++++++++++++++- .../Contracts/DotNetPreviewSDKAPI.json | 10 +- 5 files changed, 132 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 2fa183993d..444c4ff53a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1761,8 +1761,9 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// A feed range that represents a parent range. /// A feed range tha represents a child range. + /// A cancellation token. /// True or False - public virtual bool IsSubset(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange) + public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 08f10bc126..b9d8d6094c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -25,7 +25,8 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; - using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -1255,22 +1256,33 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( } #if PREVIEW - public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRange) + public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { - return Documents.Routing.Range.CheckOverlapping( - range1: ContainerCore.ConvertToRange(parentFeedRange), - range2: ContainerCore.ConvertToRange(childFeedRange)); - } -#endif + FeedRangeInternal parentFeedRangeInternal = (FeedRangeInternal)parentFeedRange; + FeedRangeInternal childFeedRangeInternal = (FeedRangeInternal)childFeedRange; + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + ContainerResponse containerResponse = await this.ReadContainerAsync(); + IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + + List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerResponse.Resource.ResourceId, + partitionKeyDefinition: partitionKeyDefinition, + trace: NoOpTrace.Singleton); - private static IEnumerable> ConvertToRange(IReadOnlyList fromFeedRanges) - { - foreach (FeedRange fromFeedRange in fromFeedRanges) - { - yield return ContainerCore.ConvertToRange(fromFeedRange); - } - } + List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerResponse.Resource.ResourceId, + partitionKeyDefinition: partitionKeyDefinition, + trace: NoOpTrace.Singleton); - private static Documents.Routing.Range ConvertToRange(FeedRange fromFeedRange) => ((FeedRangeEpk)fromFeedRange).Range; + return parentRanges + .SelectMany(parentRange => childRanges, (parentRange, childRange) => new { parentRange, childRange }) + .Where(pair => Documents.Routing.Range.CheckOverlapping( + range1: pair.parentRange, + range2: pair.childRange)) + .FirstOrDefault() != null; + } +#endif } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 888d19fddc..decc4c6f7d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -662,9 +662,9 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } #if PREVIEW - public override bool IsSubset(FeedRange parentFeedRange, FeedRange childFeedRange) + public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken) { - return base.IsSubset(parentFeedRange, childFeedRange); + return await base.IsSubsetAsync(parentFeedRange, childFeedRange, cancellationToken); } #endif } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index b534fe1e0f..35051cce7c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -9,7 +9,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Collections.ObjectModel; using System.IO; using System.Linq; - using System.Net; + using System.Net; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -1784,6 +1785,94 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container } #if PREVIEW + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("", "FFFFFFFFFFFFFFFF", true)] // Full range. + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. + [Description("Given a parent feed range and a partition key, when the partition key is converted to a feed range, then that feed range is checked" + + "against the parent feed range to determine if it is a subset of the parent feed range.")] + public async Task GivenParentFeedRangeAndChildPartitionKeyIsSubsetTestAsync( + string parentMin, + string parentMax, + bool expectedIsSubset) + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + PartitionKey partitionKey = new("WA"); + FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); + + bool actualIsSubset = await container.IsSubsetAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("", "FFFFFFFFFFFFFFFF", true)] // Full range. + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. + [Description("Given a parent feed range and a hierarchical partition key, when the hierarchical partition key is converted to a feed range, " + + "then that feed range is checked against the parent feed range to determine if it is a subset of the parent feed range.")] + public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsSubsetTestAsync( + string parentMin, + string parentMax, + bool expectedIsSubset) + { + Container container = default; + + try + { + ContainerProperties containerProperties = new ContainerProperties() + { + Id = Guid.NewGuid().ToString(), + PartitionKeyPaths = new Collection { "/pk", "/id" } + }; + + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(containerProperties); + + container = containerResponse.Container; + + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add("WA") + .Add(Guid.NewGuid().ToString()) + .Build(); + + FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); + + bool actualIsSubset = await container.IsSubsetAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] @@ -1796,6 +1885,8 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] + [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + + "to determine if it is a subset of the parent feed range.")] public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( string childMin, string childMax, @@ -1807,16 +1898,16 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( try { - PartitionKey partitionKey = new("WA"); ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk"); container = containerResponse.Container; - bool actualIsSubset = container.IsSubset( + bool actualIsSubset = await container.IsSubsetAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, true, false))); + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, true, false)), + cancellationToken: CancellationToken.None); Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } @@ -1828,6 +1919,6 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( } } } -#endif +#endif } } 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 c82d2d6c40..e1cf0de849 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 @@ -299,11 +299,6 @@ "Microsoft.Azure.Cosmos.Container;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Boolean IsSubset(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean IsSubset(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1)": { "Type": "Method", "Attributes": [], @@ -314,6 +309,11 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], From 77a18972741661e0948a424f163c9fbc315e65cd Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 16 Aug 2024 10:38:59 -0400 Subject: [PATCH 049/145] some subset changes. and tests. --- .../Resource/Container/ContainerCore.Items.cs | 74 ++++++++++++------- .../CosmosContainerTests.cs | 61 ++++++++++++--- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index b9d8d6094c..8552527630 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -25,8 +25,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; - using Microsoft.Azure.Cosmos.ReadFeed.Pagination; - using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.ReadFeed.Pagination; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -1254,35 +1253,58 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( changeFeedProcessor: changeFeedProcessor, applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } - #if PREVIEW public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { - FeedRangeInternal parentFeedRangeInternal = (FeedRangeInternal)parentFeedRange; - FeedRangeInternal childFeedRangeInternal = (FeedRangeInternal)childFeedRange; - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - ContainerResponse containerResponse = await this.ReadContainerAsync(); - IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - - List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerResponse.Resource.ResourceId, - partitionKeyDefinition: partitionKeyDefinition, - trace: NoOpTrace.Singleton); + if (parentFeedRange is FeedRangeInternal parentFeedRangeInternal && childFeedRange is FeedRangeInternal childFeedRangeInternal) + { + using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) + { + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + string containerRId = await this.GetCachedRIDAsync( + forceRefresh: false, + trace: trace, + cancellationToken: cancellationToken); + + IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + + List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); + + List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); - List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerResponse.Resource.ResourceId, - partitionKeyDefinition: partitionKeyDefinition, - trace: NoOpTrace.Singleton); + var overlaps = parentRanges + .SelectMany(parentRange => childRanges, (parentRange, childRange) => new { parentRange, childRange }) + .Where(pair => Documents.Routing.Range.CheckOverlapping( + range1: pair.parentRange, + range2: pair.childRange)) + .FirstOrDefault(); + + return ContainerCore.IsSubset(overlaps); + } + } + else + { + throw new ArgumentException($"Arguments for '{nameof(parentFeedRange)}' and '{nameof(childFeedRange)}' are not supported."); + } + } + + private static bool IsSubset(dynamic overlaps) + { + if (overlaps == null) return false; - return parentRanges - .SelectMany(parentRange => childRanges, (parentRange, childRange) => new { parentRange, childRange }) - .Where(pair => Documents.Routing.Range.CheckOverlapping( - range1: pair.parentRange, - range2: pair.childRange)) - .FirstOrDefault() != null; + return String.Compare(overlaps.childRange.Min, overlaps.parentRange.Min) > 0 + && String.Compare(overlaps.childRange.Min, overlaps.parentRange.Max) < 0 + && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Min) > 0 + && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Max) < 0; } -#endif +#endif } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 35051cce7c..147a254fef 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -13,7 +13,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -1875,16 +1876,23 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsSubsetTe [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] - [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] - [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] - [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] - [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] - [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] - [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] - [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] - [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] + [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] // child is same of the parent, which makes it a subset + [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("4CCCCCCCCCCCCCCC", "5999999999999999", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("5999999999999999", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("6666666666666666", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7333333333333333", "7FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7333333333333333", "FFFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent + [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + "to determine if it is a subset of the parent feed range.")] public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( @@ -1919,6 +1927,37 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( } } } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenMockedFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + ArgumentException argumentException = await Assert.ThrowsExceptionAsync( + async () => await container.IsSubsetAsync( + parentFeedRange: Mock.Of(), + childFeedRange: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(argumentException); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } #endif } } From 8f07db85552ca931e2778ab3a4a506954cf8ba7a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 16 Aug 2024 10:47:04 -0400 Subject: [PATCH 050/145] missing using directive --- .../src/Resource/Container/ContainerCore.Items.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8552527630..0f40af4c8f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -25,7 +25,8 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; - using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -1305,6 +1306,6 @@ private static bool IsSubset(dynamic overlaps) && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Min) > 0 && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Max) < 0; } -#endif +#endif } } From 6aa94875770581f5d57b27cb778735519c217c81 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 08:15:14 -0400 Subject: [PATCH 051/145] type Overlaps instead of dynamic type. Fix IsSubset to include equal to. --- .../Resource/Container/ContainerCore.Items.cs | 20 +++++++------- .../src/Resource/Container/Overlaps.cs | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 0f40af4c8f..8bd7195a8c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -27,6 +27,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -1254,6 +1255,7 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( changeFeedProcessor: changeFeedProcessor, applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } + #if PREVIEW public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { @@ -1281,11 +1283,11 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa partitionKeyDefinition: partitionKeyDefinition, trace: trace); - var overlaps = parentRanges - .SelectMany(parentRange => childRanges, (parentRange, childRange) => new { parentRange, childRange }) + Overlaps overlaps = parentRanges + .SelectMany(parentRange => childRanges, (parentRange, childRange) => Overlaps.Create(parentRange, childRange)) .Where(pair => Documents.Routing.Range.CheckOverlapping( - range1: pair.parentRange, - range2: pair.childRange)) + range1: pair.ParentRange, + range2: pair.ChildRange)) .FirstOrDefault(); return ContainerCore.IsSubset(overlaps); @@ -1297,14 +1299,14 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa } } - private static bool IsSubset(dynamic overlaps) + private static bool IsSubset(Overlaps overlaps) { if (overlaps == null) return false; - return String.Compare(overlaps.childRange.Min, overlaps.parentRange.Min) > 0 - && String.Compare(overlaps.childRange.Min, overlaps.parentRange.Max) < 0 - && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Min) > 0 - && String.Compare(overlaps.childRange.Max, overlaps.parentRange.Max) < 0; + return String.Compare(overlaps.ChildRange.Min, overlaps.ParentRange.Min) >= 0 + && String.Compare(overlaps.ChildRange.Min, overlaps.ParentRange.Max) <= 0 + && String.Compare(overlaps.ChildRange.Max, overlaps.ParentRange.Min) >= 0 + && String.Compare(overlaps.ChildRange.Max, overlaps.ParentRange.Max) <= 0; } #endif } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs new file mode 100644 index 0000000000..004a872081 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs @@ -0,0 +1,27 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// A type that represents a parent range and a child range that overlaps, not a subset. + /// + internal class Overlaps + { + public Documents.Routing.Range ParentRange { get; } + + public Documents.Routing.Range ChildRange { get; } + + public static Overlaps Create(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) + { + return new Overlaps(parentRange, childRange); + } + + private Overlaps(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) + { + this.ParentRange = parentRange; + this.ChildRange = childRange; + } + } +} From 8e96a69d36a4fbda2116b97c1441450e070c1316 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 14:15:56 -0400 Subject: [PATCH 052/145] Overlaps list --- .../Resource/Container/ContainerCore.Items.cs | 25 ++++++++++++------- .../Container/{Overlaps.cs => Overlap.cs} | 8 +++--- 2 files changed, 20 insertions(+), 13 deletions(-) rename Microsoft.Azure.Cosmos/src/Resource/Container/{Overlaps.cs => Overlap.cs} (66%) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8bd7195a8c..7fbe1ddf54 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1283,12 +1283,11 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa partitionKeyDefinition: partitionKeyDefinition, trace: trace); - Overlaps overlaps = parentRanges - .SelectMany(parentRange => childRanges, (parentRange, childRange) => Overlaps.Create(parentRange, childRange)) + IEnumerable overlaps = parentRanges + .SelectMany(parentRange => childRanges, (parentRange, childRange) => Overlap.Create(parentRange, childRange)) .Where(pair => Documents.Routing.Range.CheckOverlapping( range1: pair.ParentRange, - range2: pair.ChildRange)) - .FirstOrDefault(); + range2: pair.ChildRange)); return ContainerCore.IsSubset(overlaps); } @@ -1299,14 +1298,22 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa } } - private static bool IsSubset(Overlaps overlaps) + private static bool IsSubset(IEnumerable overlaps) { if (overlaps == null) return false; - return String.Compare(overlaps.ChildRange.Min, overlaps.ParentRange.Min) >= 0 - && String.Compare(overlaps.ChildRange.Min, overlaps.ParentRange.Max) <= 0 - && String.Compare(overlaps.ChildRange.Max, overlaps.ParentRange.Min) >= 0 - && String.Compare(overlaps.ChildRange.Max, overlaps.ParentRange.Max) <= 0; + foreach (Overlap overlap in overlaps) + { + if (String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Min) >= 0 + && String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Max) <= 0 + && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Min) >= 0 + && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Max) <= 0) + { + return true; + } + } + + return false; } #endif } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs similarity index 66% rename from Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs rename to Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs index 004a872081..313072acc4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Overlaps.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs @@ -7,18 +7,18 @@ namespace Microsoft.Azure.Cosmos /// /// A type that represents a parent range and a child range that overlaps, not a subset. /// - internal class Overlaps + internal class Overlap { public Documents.Routing.Range ParentRange { get; } public Documents.Routing.Range ChildRange { get; } - public static Overlaps Create(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) + public static Overlap Create(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { - return new Overlaps(parentRange, childRange); + return new Overlap(parentRange, childRange); } - private Overlaps(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) + private Overlap(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { this.ParentRange = parentRange; this.ChildRange = childRange; From 72770f24496f484f111df6bedcebd12d18174208 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 20:06:49 -0400 Subject: [PATCH 053/145] update logic for IsSubset --- .../Resource/Container/ContainerCore.Items.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 7fbe1ddf54..065f4c1191 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1277,19 +1277,25 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa partitionKeyDefinition: partitionKeyDefinition, trace: trace); + Documents.Routing.Range parentRange = new Documents.Routing.Range( + min: parentRanges.FirstOrDefault().Min, + max: parentRanges.LastOrDefault().Max, + isMaxInclusive: true, + isMinInclusive: false); + List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace); - IEnumerable overlaps = parentRanges - .SelectMany(parentRange => childRanges, (parentRange, childRange) => Overlap.Create(parentRange, childRange)) - .Where(pair => Documents.Routing.Range.CheckOverlapping( - range1: pair.ParentRange, - range2: pair.ChildRange)); + Documents.Routing.Range childRange = new Documents.Routing.Range( + min: childRanges.FirstOrDefault().Min, + max: childRanges.LastOrDefault().Max, + isMaxInclusive: true, + isMinInclusive: false); - return ContainerCore.IsSubset(overlaps); + return ContainerCore.IsSubset(Overlap.Create(parentRange: parentRange, childRange: childRange)); } } else @@ -1298,22 +1304,14 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa } } - private static bool IsSubset(IEnumerable overlaps) + private static bool IsSubset(Overlap overlap) { - if (overlaps == null) return false; - - foreach (Overlap overlap in overlaps) - { - if (String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Min) >= 0 - && String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Max) <= 0 - && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Min) >= 0 - && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Max) <= 0) - { - return true; - } - } + if (overlap == null) return false; - return false; + return String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Min) >= 0 + && String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Max) <= 0 + && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Min) >= 0 + && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Max) <= 0; } #endif } From 83992d8f0972e291b235d6f1cf69158f4ac0f0ce Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 20:12:38 -0400 Subject: [PATCH 054/145] removed Overlap type. No longer needed. --- .../Resource/Container/ContainerCore.Items.cs | 14 +++++----- .../src/Resource/Container/Overlap.cs | 27 ------------------- 2 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 065f4c1191..8a5e72d749 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1295,7 +1295,7 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa isMaxInclusive: true, isMinInclusive: false); - return ContainerCore.IsSubset(Overlap.Create(parentRange: parentRange, childRange: childRange)); + return ContainerCore.IsSubset(parentRange: parentRange, childRange: childRange); } } else @@ -1304,14 +1304,12 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa } } - private static bool IsSubset(Overlap overlap) + private static bool IsSubset(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { - if (overlap == null) return false; - - return String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Min) >= 0 - && String.Compare(overlap.ChildRange.Min, overlap.ParentRange.Max) <= 0 - && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Min) >= 0 - && String.Compare(overlap.ChildRange.Max, overlap.ParentRange.Max) <= 0; + return String.Compare(childRange.Min, parentRange.Min) >= 0 + && String.Compare(childRange.Min, parentRange.Max) <= 0 + && String.Compare(childRange.Max, parentRange.Min) >= 0 + && String.Compare(childRange.Max, parentRange.Max) <= 0; } #endif } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs deleted file mode 100644 index 313072acc4..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Overlap.cs +++ /dev/null @@ -1,27 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - /// - /// A type that represents a parent range and a child range that overlaps, not a subset. - /// - internal class Overlap - { - public Documents.Routing.Range ParentRange { get; } - - public Documents.Routing.Range ChildRange { get; } - - public static Overlap Create(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) - { - return new Overlap(parentRange, childRange); - } - - private Overlap(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) - { - this.ParentRange = parentRange; - this.ChildRange = childRange; - } - } -} From a7119a21351e53a979cc76d944d6ed03d75001b2 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 14:46:04 -0400 Subject: [PATCH 055/145] ArgumentException to include expect and actual type. --- .../Resource/Container/ContainerCore.Items.cs | 73 ++++++++++--------- .../CosmosContainerTests.cs | 66 ++++++++++++++++- 2 files changed, 101 insertions(+), 38 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8a5e72d749..9b60d3ee5d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1259,48 +1259,51 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( #if PREVIEW public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { - if (parentFeedRange is FeedRangeInternal parentFeedRangeInternal && childFeedRange is FeedRangeInternal childFeedRangeInternal) + if (parentFeedRange is not FeedRangeInternal parentFeedRangeInternal) { - using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) - { - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - string containerRId = await this.GetCachedRIDAsync( - forceRefresh: false, - trace: trace, - cancellationToken: cancellationToken); + throw new ArgumentException($"The argument for '{nameof(parentFeedRange)}' must be of type {typeof(FeedRange)} but was {parentFeedRange?.GetType().Name ?? "null"}"); + } + + if (childFeedRange is not FeedRangeInternal childFeedRangeInternal) + { + throw new ArgumentException($"The argument for '{nameof(childFeedRange)}' must be of type {typeof(FeedRange)} but was {childFeedRange?.GetType().Name ?? "null"}"); + } - IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) + { + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + string containerRId = await this.GetCachedRIDAsync( + forceRefresh: false, + trace: trace, + cancellationToken: cancellationToken); - List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace); + IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - Documents.Routing.Range parentRange = new Documents.Routing.Range( - min: parentRanges.FirstOrDefault().Min, - max: parentRanges.LastOrDefault().Max, - isMaxInclusive: true, - isMinInclusive: false); + List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); - List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace); + Documents.Routing.Range parentRange = new Documents.Routing.Range( + min: parentRanges.First().Min, + max: parentRanges.Last().Max, + isMaxInclusive: true, + isMinInclusive: false); - Documents.Routing.Range childRange = new Documents.Routing.Range( - min: childRanges.FirstOrDefault().Min, - max: childRanges.LastOrDefault().Max, - isMaxInclusive: true, - isMinInclusive: false); + List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); - return ContainerCore.IsSubset(parentRange: parentRange, childRange: childRange); - } - } - else - { - throw new ArgumentException($"Arguments for '{nameof(parentFeedRange)}' and '{nameof(childFeedRange)}' are not supported."); + Documents.Routing.Range childRange = new Documents.Routing.Range( + min: childRanges.First().Min, + max: childRanges.Last().Max, + isMaxInclusive: true, + isMinInclusive: false); + + return ContainerCore.IsSubset(parentRange: parentRange, childRange: childRange); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 147a254fef..27833fbc5f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; @@ -1930,7 +1931,64 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenMockedFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + public async Task GivenMockedParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + { + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(Mock.Of()); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenNullParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + { + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(default); + } + + private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(FeedRange feedRange) + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + ArgumentException argumentException = await Assert.ThrowsExceptionAsync( + async () => await container.IsSubsetAsync( + parentFeedRange: feedRange, + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(argumentException); + Logger.LogLine(argumentException.Message); + Assert.IsTrue(argumentException.Message.Contains($"The argument for 'parentFeedRange' must be of type Microsoft.Azure.Cosmos.FeedRange but was {feedRange?.GetType().Name ?? "null"}")); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenMockedChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + { + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(Mock.Of()); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenNullChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + { + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(default); + } + + private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(FeedRange feedRange) { Container container = default; @@ -1944,11 +2002,13 @@ public async Task GivenMockedFeedRangeExpectsArgumentExceptionIsSubsetTestAsync( ArgumentException argumentException = await Assert.ThrowsExceptionAsync( async () => await container.IsSubsetAsync( - parentFeedRange: Mock.Of(), - childFeedRange: Mock.Of(), + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + childFeedRange: feedRange, cancellationToken: CancellationToken.None)); Assert.IsNotNull(argumentException); + Logger.LogLine(argumentException.Message); + Assert.IsTrue(argumentException.Message.Contains($"The argument for 'childFeedRange' must be of type Microsoft.Azure.Cosmos.FeedRange but was {feedRange?.GetType().Name ?? "null"}")); } finally { From 354dbbe6abe3ca65a13c777e8ac8bae52500eb2f Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 15:06:29 -0400 Subject: [PATCH 056/145] include basic code sample references in code-doc. --- .../src/Resource/Container/Container.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 444c4ff53a..19609512d7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1762,6 +1762,24 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// A feed range that represents a parent range. /// A feed range tha represents a child range. /// A cancellation token. + /// + /// + /// + /// + /// /// True or False public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken) { From 7026c3b733ffb77ed32712a759d84284d64307da Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 15:07:43 -0400 Subject: [PATCH 057/145] default argument for CancellationToken parameter --- Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 19609512d7..1082e3e277 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1781,7 +1781,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True or False - public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken) + public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } From 68e15c580a5bd56b9e1e684e0fc23af42bafb826 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 15:11:41 -0400 Subject: [PATCH 058/145] removed NoOpTrace --- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 9b60d3ee5d..6628fd104b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1277,7 +1277,7 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa trace: trace, cancellationToken: cancellationToken); - IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, From efc7c8ce6838e5de2b24e925bcffc4a9f4f80f68 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 15:22:47 -0400 Subject: [PATCH 059/145] another default argument for CancellationToken parameter --- .../src/Resource/Container/ContainerInlineCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index decc4c6f7d..081862e87b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -662,7 +662,7 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } #if PREVIEW - public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken) + public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { return await base.IsSubsetAsync(parentFeedRange, childFeedRange, cancellationToken); } From bd8f56a4b67f519b7e34e2fc2c7291cf44aff37c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 21 Aug 2024 16:01:18 -0400 Subject: [PATCH 060/145] discovering the smallest Min and the largest Max from an array of Documents.Routing.Range --- .../src/Resource/Container/ContainerCore.Items.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 6628fd104b..39b08c386a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1286,8 +1286,8 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa trace: trace); Documents.Routing.Range parentRange = new Documents.Routing.Range( - min: parentRanges.First().Min, - max: parentRanges.Last().Max, + min: parentRanges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min. + max: parentRanges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max. isMaxInclusive: true, isMinInclusive: false); @@ -1298,8 +1298,8 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa trace: trace); Documents.Routing.Range childRange = new Documents.Routing.Range( - min: childRanges.First().Min, - max: childRanges.Last().Max, + min: childRanges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min. + max: childRanges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max. isMaxInclusive: true, isMinInclusive: false); From 36c3e5ffe019b8336e322c46fcd4a916376a3ee6 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 22 Aug 2024 10:01:14 -0400 Subject: [PATCH 061/145] some refactoring. also dealing with ranges that are FeedRangePartitionKey --- .../src/Resource/Container/Container.cs | 7 +- .../Resource/Container/ContainerCore.Items.cs | 95 +++++++++++++------ .../Resource/Container/ContainerInlineCore.cs | 10 +- 3 files changed, 77 insertions(+), 35 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 1082e3e277..6f333876b6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1756,7 +1756,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV string processorName, ChangeFeedHandler> onChangesDelegate); - /// + /// /// Takes 2 given feed ranges representing a parent and child feed range and checks if the child feed range is a subset of the parent feed range. /// /// A feed range that represents a parent range. @@ -1781,7 +1781,10 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True or False - public virtual Task IsSubsetAsync(Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) + public virtual Task IsSubsetAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 39b08c386a..f063f23a13 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -30,8 +30,9 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; - using Microsoft.Azure.Documents; - + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + /// /// Used to perform operations on items. There are two different types of operations. /// 1. The object operations where it serializes and deserializes the item on request/response @@ -1257,7 +1258,10 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( } #if PREVIEW - public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) + public override async Task IsSubsetAsync( + FeedRange parentFeedRange, + FeedRange childFeedRange, + CancellationToken cancellationToken = default) { if (parentFeedRange is not FeedRangeInternal parentFeedRangeInternal) { @@ -1272,6 +1276,7 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) { PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + string containerRId = await this.GetCachedRIDAsync( forceRefresh: false, trace: trace, @@ -1279,40 +1284,68 @@ public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRa IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); - List> parentRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace); - - Documents.Routing.Range parentRange = new Documents.Routing.Range( - min: parentRanges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min. - max: parentRanges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max. - isMaxInclusive: true, - isMinInclusive: false); + return ContainerCore.IsSubset( + range1: await this.GetRangeAsync( + feedRangeInternal: parentFeedRangeInternal, + partitionKeyDefinition: partitionKeyDefinition, + containerRId: containerRId, + feedRange: parentFeedRange, + ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace), + routingMapProvider: routingMapProvider, + trace: trace), + range2: await this.GetRangeAsync( + feedRangeInternal: childFeedRangeInternal, + partitionKeyDefinition: partitionKeyDefinition, + containerRId: containerRId, + feedRange: childFeedRange, + ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace), + routingMapProvider: routingMapProvider, + trace: trace)); + } + } - List> childRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace); + private async Task> GetRangeAsync( + Cosmos.FeedRangeInternal feedRangeInternal, + PartitionKeyDefinition partitionKeyDefinition, + string containerRId, + Cosmos.FeedRange feedRange, + List> ranges, + IRoutingMapProvider routingMapProvider, + ITrace trace) + { + List> parentRanges = await feedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); - Documents.Routing.Range childRange = new Documents.Routing.Range( - min: childRanges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min. - max: childRanges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max. - isMaxInclusive: true, - isMinInclusive: false); + // NOTE(philipthomas-MSFT): If FeedRangePartitionKey, it's Min and Max are the same, set minInclusive and maxInclusive is both set to True. - return ContainerCore.IsSubset(parentRange: parentRange, childRange: childRange); - } + return feedRange is FeedRangePartitionKey + ? Documents.Routing.Range.GetPointRange(ranges.Min(range => range.Min)) + : new Documents.Routing.Range( + min: ranges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min because no gaurantee of sorting. + max: ranges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max because no gaurantee of sorting. + isMinInclusive: true, + isMaxInclusive: false); } - private static bool IsSubset(Documents.Routing.Range parentRange, Documents.Routing.Range childRange) + private static bool IsSubset( + Documents.Routing.Range range1, + Documents.Routing.Range range2) { - return String.Compare(childRange.Min, parentRange.Min) >= 0 - && String.Compare(childRange.Min, parentRange.Max) <= 0 - && String.Compare(childRange.Max, parentRange.Min) >= 0 - && String.Compare(childRange.Max, parentRange.Max) <= 0; + // NOTE(philipthomas-MSFT: May want to add this to Range.cs within Microsoft.Azure.Cosmos.Direct in the future. + + return range1.Contains(range2.Min) + && (range1.Max == range2.Max || range1.Contains(range2.Max)); } #endif } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 081862e87b..4672966505 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -662,9 +662,15 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } #if PREVIEW - public override async Task IsSubsetAsync(FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) + public override async Task IsSubsetAsync( + FeedRange parentFeedRange, + FeedRange childFeedRange, + CancellationToken cancellationToken = default) { - return await base.IsSubsetAsync(parentFeedRange, childFeedRange, cancellationToken); + return await base.IsSubsetAsync( + parentFeedRange, + childFeedRange, + cancellationToken); } #endif } From 74f7faba0e038015af714dd77f1904786607b4e9 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 22 Aug 2024 10:12:34 -0400 Subject: [PATCH 062/145] removed an unnecessary getEffectoveRamgesAsync call. --- .../src/Resource/Container/Container.cs | 2 +- .../Resource/Container/ContainerCore.Items.cs | 33 ++++--------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 6f333876b6..fc29d6d2b4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1756,7 +1756,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV string processorName, ChangeFeedHandler> onChangesDelegate); - /// + /// /// Takes 2 given feed ranges representing a parent and child feed range and checks if the child feed range is a subset of the parent feed range. /// /// A feed range that represents a parent range. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index f063f23a13..f2b2069300 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1285,48 +1285,27 @@ public override async Task IsSubsetAsync( IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); return ContainerCore.IsSubset( - range1: await this.GetRangeAsync( - feedRangeInternal: parentFeedRangeInternal, - partitionKeyDefinition: partitionKeyDefinition, - containerRId: containerRId, + range1: ContainerCore.GetRange( feedRange: parentFeedRange, ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, - trace: trace), - routingMapProvider: routingMapProvider, - trace: trace), - range2: await this.GetRangeAsync( - feedRangeInternal: childFeedRangeInternal, - partitionKeyDefinition: partitionKeyDefinition, - containerRId: containerRId, + trace: trace)), + range2: ContainerCore.GetRange( feedRange: childFeedRange, ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, - trace: trace), - routingMapProvider: routingMapProvider, - trace: trace)); + trace: trace))); } } - private async Task> GetRangeAsync( - Cosmos.FeedRangeInternal feedRangeInternal, - PartitionKeyDefinition partitionKeyDefinition, - string containerRId, + private static Documents.Routing.Range GetRange( Cosmos.FeedRange feedRange, - List> ranges, - IRoutingMapProvider routingMapProvider, - ITrace trace) + List> ranges) { - List> parentRanges = await feedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace); - // NOTE(philipthomas-MSFT): If FeedRangePartitionKey, it's Min and Max are the same, set minInclusive and maxInclusive is both set to True. return feedRange is FeedRangePartitionKey From a381687ff7a7baf681ddd9f689fd780b5afc55fd Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 22 Aug 2024 10:59:45 -0400 Subject: [PATCH 063/145] not sure why this changed. --- .../src/Resource/Container/ContainerCore.cs | 102 +++++++++--------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 02b38601e3..45dd733824 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -6,13 +6,16 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; - using System.IO; + using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed; + using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Pagination; + using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; @@ -253,7 +256,7 @@ public Task ReplaceContainerStreamAsync( requestOptions: requestOptions, trace: trace, cancellationToken: cancellationToken); - } + } public async Task> GetFeedRangesAsync( ITrace trace, @@ -267,57 +270,50 @@ public async Task> GetFeedRangesAsync( trace, cancellationToken); - try - { - IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - containerRId, - ContainerCore.allRanges, - trace, - forceRefresh: true); - - if (partitionKeyRanges == null) - { - string refreshedContainerRId; - refreshedContainerRId = await this.GetCachedRIDAsync( - forceRefresh: true, - trace, - cancellationToken); - - if (string.Equals(containerRId, refreshedContainerRId)) - { - throw CosmosExceptionFactory.CreateInternalServerErrorException( - $"Container rid {containerRId} did not have a partition key range after refresh", - headers: new Headers(), - trace: trace); - } - - partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - refreshedContainerRId, - ContainerCore.allRanges, - trace, - forceRefresh: true); - - if (partitionKeyRanges == null) - { - throw CosmosExceptionFactory.CreateInternalServerErrorException( - $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh", - headers: new Headers(), - trace: trace); - } - } - - List feedTokens = new List(partitionKeyRanges.Count); - foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) - { - feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange())); - } - - return feedTokens; - } - catch (DocumentClientException dce) - { - throw CosmosExceptionFactory.Create(dce, trace); + IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + containerRId, + ContainerCore.allRanges, + trace, + forceRefresh: true); + + if (partitionKeyRanges == null) + { + string refreshedContainerRId; + refreshedContainerRId = await this.GetCachedRIDAsync( + forceRefresh: true, + trace, + cancellationToken); + + if (string.Equals(containerRId, refreshedContainerRId)) + { + throw CosmosExceptionFactory.CreateInternalServerErrorException( + $"Container rid {containerRId} did not have a partition key range after refresh", + headers: new Headers(), + trace: trace); + } + + partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + refreshedContainerRId, + ContainerCore.allRanges, + trace, + forceRefresh: true); + + if (partitionKeyRanges == null) + { + throw CosmosExceptionFactory.CreateInternalServerErrorException( + $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh", + headers: new Headers(), + trace: trace); + } } + + List feedTokens = new List(partitionKeyRanges.Count); + foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) + { + feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange())); + } + + return feedTokens; } public override FeedIterator GetChangeFeedStreamIterator( @@ -703,6 +699,6 @@ public override FeedIterator GetChangeFeedIteratorWithQuery( return new FeedIteratorCore( changeFeedIteratorCore, responseCreator: this.ClientContext.ResponseFactory.CreateChangeFeedUserTypeResponse); - } + } } } \ No newline at end of file From edf36d49008cfebd66c642e8448a24a702708570 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 22 Aug 2024 11:05:26 -0400 Subject: [PATCH 064/145] hmm. add try catch back. --- .../src/Resource/Container/ContainerCore.cs | 95 ++++++++++--------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 45dd733824..52ab3eae3c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -269,51 +269,58 @@ public async Task> GetFeedRangesAsync( forceRefresh: false, trace, cancellationToken); - - IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - containerRId, - ContainerCore.allRanges, - trace, - forceRefresh: true); - - if (partitionKeyRanges == null) - { - string refreshedContainerRId; - refreshedContainerRId = await this.GetCachedRIDAsync( - forceRefresh: true, - trace, - cancellationToken); - - if (string.Equals(containerRId, refreshedContainerRId)) - { - throw CosmosExceptionFactory.CreateInternalServerErrorException( - $"Container rid {containerRId} did not have a partition key range after refresh", - headers: new Headers(), - trace: trace); - } - - partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - refreshedContainerRId, - ContainerCore.allRanges, - trace, - forceRefresh: true); - - if (partitionKeyRanges == null) - { - throw CosmosExceptionFactory.CreateInternalServerErrorException( - $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh", - headers: new Headers(), - trace: trace); - } - } - - List feedTokens = new List(partitionKeyRanges.Count); - foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) - { - feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange())); + + try + { + IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + containerRId, + ContainerCore.allRanges, + trace, + forceRefresh: true); + + if (partitionKeyRanges == null) + { + string refreshedContainerRId; + refreshedContainerRId = await this.GetCachedRIDAsync( + forceRefresh: true, + trace, + cancellationToken); + + if (string.Equals(containerRId, refreshedContainerRId)) + { + throw CosmosExceptionFactory.CreateInternalServerErrorException( + $"Container rid {containerRId} did not have a partition key range after refresh", + headers: new Headers(), + trace: trace); + } + + partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + refreshedContainerRId, + ContainerCore.allRanges, + trace, + forceRefresh: true); + + if (partitionKeyRanges == null) + { + throw CosmosExceptionFactory.CreateInternalServerErrorException( + $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh", + headers: new Headers(), + trace: trace); + } + } + + List feedTokens = new List(partitionKeyRanges.Count); + foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges) + { + feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange())); + } + + return feedTokens; + } + catch (DocumentClientException dce) + { + throw CosmosExceptionFactory.Create(dce, trace); } - - return feedTokens; } public override FeedIterator GetChangeFeedStreamIterator( From bbed6d49eceb08c1562f9a3fa715c060a9cfce20 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 6 Sep 2024 17:57:25 -0400 Subject: [PATCH 065/145] name refactoring. TryParse on parent and child json. DocumentClientException catch. removed mock test on parent and child since we are nothing limiting to types, but by parsing of json. --- .../src/Resource/Container/Container.cs | 4 +- .../Resource/Container/ContainerCore.Items.cs | 73 +++++++++++-------- .../Resource/Container/ContainerInlineCore.cs | 4 +- .../CosmosContainerTests.cs | 33 +++------ .../Contracts/DotNetPreviewSDKAPI.json | 4 +- 5 files changed, 57 insertions(+), 61 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index fc29d6d2b4..65ecf6ed65 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1773,7 +1773,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// FeedRange parentFeedRange = ...; /// FeedRange childFeedRange = ...; /// - /// bool isSubset = await container.IsSubsetAsync( + /// bool isSubset = await container.IsFeedRangePartOfAsync( /// parentFeedRange, /// childFeedRange, /// cancellationToken); @@ -1781,7 +1781,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True or False - public virtual Task IsSubsetAsync( + public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index f2b2069300..9b7072a7c6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -26,6 +26,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Azure.Cosmos.Serializer; @@ -1258,47 +1259,57 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( } #if PREVIEW - public override async Task IsSubsetAsync( + public override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { - if (parentFeedRange is not FeedRangeInternal parentFeedRangeInternal) + using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) { - throw new ArgumentException($"The argument for '{nameof(parentFeedRange)}' must be of type {typeof(FeedRange)} but was {parentFeedRange?.GetType().Name ?? "null"}"); - } + if (parentFeedRange == null) + { + throw new ArgumentNullException($"The argument for '{nameof(parentFeedRange)}' cannot be null."); + } - if (childFeedRange is not FeedRangeInternal childFeedRangeInternal) - { - throw new ArgumentException($"The argument for '{nameof(childFeedRange)}' must be of type {typeof(FeedRange)} but was {childFeedRange?.GetType().Name ?? "null"}"); - } + if (childFeedRange == null) + { + throw new ArgumentNullException($"The argument for '{nameof(childFeedRange)}' cannot be null."); + } - using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) - { - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); + try + { + _ = FeedRangeInternal.TryParse(parentFeedRange?.ToJsonString(), out FeedRangeInternal parentFeedRangeInternal); + _ = FeedRangeInternal.TryParse(childFeedRange?.ToJsonString(), out FeedRangeInternal childFeedRangeInternal); + + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); - string containerRId = await this.GetCachedRIDAsync( - forceRefresh: false, - trace: trace, - cancellationToken: cancellationToken); + string containerRId = await this.GetCachedRIDAsync( + forceRefresh: false, + trace: trace, + cancellationToken: cancellationToken); - IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); + IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); - return ContainerCore.IsSubset( - range1: ContainerCore.GetRange( - feedRange: parentFeedRange, - ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace)), - range2: ContainerCore.GetRange( - feedRange: childFeedRange, - ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace))); + return ContainerCore.IsSubset( + range1: ContainerCore.GetRange( + feedRange: parentFeedRange, + ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace)), + range2: ContainerCore.GetRange( + feedRange: childFeedRange, + ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace))); + } + catch (DocumentClientException dce) + { + throw CosmosExceptionFactory.Create(dce, trace); + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 4672966505..0a90176938 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -662,12 +662,12 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } #if PREVIEW - public override async Task IsSubsetAsync( + public override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) { - return await base.IsSubsetAsync( + return await base.IsFeedRangePartOfAsync( parentFeedRange, childFeedRange, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 27833fbc5f..1f1a8313ab 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -1811,7 +1810,7 @@ public async Task GivenParentFeedRangeAndChildPartitionKeyIsSubsetTestAsync( PartitionKey partitionKey = new("WA"); FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - bool actualIsSubset = await container.IsSubsetAsync( + bool actualIsSubset = await container.IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); @@ -1859,7 +1858,7 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsSubsetTe FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - bool actualIsSubset = await container.IsSubsetAsync( + bool actualIsSubset = await container.IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); @@ -1913,7 +1912,7 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( container = containerResponse.Container; - bool actualIsSubset = await container.IsSubsetAsync( + bool actualIsSubset = await container.IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, true, false)), cancellationToken: CancellationToken.None); @@ -1929,13 +1928,6 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( } } - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenMockedParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() - { - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(Mock.Of()); - } - [TestMethod] [Owner("philipthomas-MSFT")] public async Task GivenNullParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() @@ -1955,15 +1947,15 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTe container = containerResponse.Container; - ArgumentException argumentException = await Assert.ThrowsExceptionAsync( - async () => await container.IsSubsetAsync( + ArgumentNullException argumentException = await Assert.ThrowsExceptionAsync( + async () => await container.IsFeedRangePartOfAsync( parentFeedRange: feedRange, childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); Assert.IsNotNull(argumentException); Logger.LogLine(argumentException.Message); - Assert.IsTrue(argumentException.Message.Contains($"The argument for 'parentFeedRange' must be of type Microsoft.Azure.Cosmos.FeedRange but was {feedRange?.GetType().Name ?? "null"}")); + Assert.IsTrue(argumentException.Message.Contains($"The argument for 'parentFeedRange' cannot be null.")); } finally { @@ -1974,13 +1966,6 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTe } } - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenMockedChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() - { - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(Mock.Of()); - } - [TestMethod] [Owner("philipthomas-MSFT")] public async Task GivenNullChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() @@ -2000,15 +1985,15 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTes container = containerResponse.Container; - ArgumentException argumentException = await Assert.ThrowsExceptionAsync( - async () => await container.IsSubsetAsync( + ArgumentNullException argumentException = await Assert.ThrowsExceptionAsync( + async () => await container.IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None)); Assert.IsNotNull(argumentException); Logger.LogLine(argumentException.Message); - Assert.IsTrue(argumentException.Message.Contains($"The argument for 'childFeedRange' must be of type Microsoft.Azure.Cosmos.FeedRange but was {feedRange?.GetType().Name ?? "null"}")); + Assert.IsTrue(argumentException.Message.Contains($"The argument for 'childFeedRange' cannot be null.")); } finally { 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 819bf266be..d37436731b 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 @@ -323,10 +323,10 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { + "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsSubsetAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", From 53848debfefd18fe76d10f99a1dc60b4daa8cd52 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 09:12:11 -0400 Subject: [PATCH 066/145] validation and error checks with tests. --- .../Resource/Container/ContainerCore.Items.cs | 11 +- .../CosmosContainerTests.cs | 103 ++++++++++++++---- 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 9b7072a7c6..0868f6f302 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1278,8 +1278,15 @@ public override async Task IsFeedRangePartOfAsync( try { - _ = FeedRangeInternal.TryParse(parentFeedRange?.ToJsonString(), out FeedRangeInternal parentFeedRangeInternal); - _ = FeedRangeInternal.TryParse(childFeedRange?.ToJsonString(), out FeedRangeInternal childFeedRangeInternal); + if (!FeedRangeInternal.TryParse(parentFeedRange.ToJsonString(), out FeedRangeInternal parentFeedRangeInternal)) + { + throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, parentFeedRange.ToJsonString())); + } + + if (!FeedRangeInternal.TryParse(childFeedRange.ToJsonString(), out FeedRangeInternal childFeedRangeInternal)) + { + throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, childFeedRange.ToJsonString())); + } PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 1f1a8313ab..f06251195e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -86,7 +87,7 @@ public async Task ReIndexingTest() while (true) { ContainerResponse readResponse = await container.ReadContainerAsync(requestOptions); - string indexTransformationStatus = readResponse.Headers["x-ms-documentdb-collection-index-transformation-progress"]; + string indexTransformationStatus = readResponse.Headers["feedRange-ms-documentdb-collection-index-transformation-progress"]; Assert.IsNotNull(indexTransformationStatus); if (int.Parse(indexTransformationStatus) == 100) @@ -1792,7 +1793,7 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. [Description("Given a parent feed range and a partition key, when the partition key is converted to a feed range, then that feed range is checked" + "against the parent feed range to determine if it is a subset of the parent feed range.")] - public async Task GivenParentFeedRangeAndChildPartitionKeyIsSubsetTestAsync( + public async Task GivenParentFeedRangeAndChildPartitionKeyIsFeedRangePartOfAsyncTestAsync( string parentMin, string parentMax, bool expectedIsSubset) @@ -1832,7 +1833,7 @@ public async Task GivenParentFeedRangeAndChildPartitionKeyIsSubsetTestAsync( [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. [Description("Given a parent feed range and a hierarchical partition key, when the hierarchical partition key is converted to a feed range, " + "then that feed range is checked against the parent feed range to determine if it is a subset of the parent feed range.")] - public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsSubsetTestAsync( + public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRangePartOfAsyncTestAsync( string parentMin, string parentMax, bool expectedIsSubset) @@ -1895,7 +1896,7 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsSubsetTe [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + "to determine if it is a subset of the parent feed range.")] - public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( + public async Task GivenParentAndChildFeedRangesIsFeedRangePartOfAsyncTestAsync( string childMin, string childMax, string parentMin, @@ -1930,12 +1931,43 @@ public async Task GivenParentAndChildFeedRangesIsSubsetTestAsync( [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenNullParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + public async Task GivenNullChildFeedRangeExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() { - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(default); + FeedRange feedRange = default; + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The argument for 'childFeedRange' cannot be null."); } - private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(FeedRange feedRange) + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenChildFeedRangeWithNoJsonExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + { + FeedRange feedRange = Mock.Of(); + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Value cannot be null. (Parameter 'value')"); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenChildFeedRangeWithInvalidJsonExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync() + { + Mock mockFeedRange = new Mock(MockBehavior.Strict); + mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); + FeedRange feedRange = mockFeedRange.Object; + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The provided string '' does not represent any known format."); + } + + private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + FeedRange feedRange, + string expectedMessage) + where TExceeption : Exception { Container container = default; @@ -1947,15 +1979,14 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTe container = containerResponse.Container; - ArgumentNullException argumentException = await Assert.ThrowsExceptionAsync( + TExceeption exception = await Assert.ThrowsExceptionAsync( async () => await container.IsFeedRangePartOfAsync( - parentFeedRange: feedRange, - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + childFeedRange: feedRange, cancellationToken: CancellationToken.None)); - Assert.IsNotNull(argumentException); - Logger.LogLine(argumentException.Message); - Assert.IsTrue(argumentException.Message.Contains($"The argument for 'parentFeedRange' cannot be null.")); + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains(expectedMessage)); } finally { @@ -1968,12 +1999,41 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsSubsetTe [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenNullChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync() + public async Task GivenNullParentFeedRangeExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() { - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(default); + FeedRange feedRange = default; + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The argument for 'parentFeedRange' cannot be null."); } - private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTestAsync(FeedRange feedRange) + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenParentFeedRangeWithNoJsonExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + { + FeedRange feedRange = Mock.Of(); + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Value cannot be null. (Parameter 'value')"); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenParentFeedRangeWithInvalidJsonExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync() + { + Mock mockFeedRange = new Mock(MockBehavior.Strict); + mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); + FeedRange feedRange = mockFeedRange.Object; + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The provided string '' does not represent any known format."); + } + + private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) + where TException : Exception { Container container = default; @@ -1985,15 +2045,14 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsSubsetTes container = containerResponse.Container; - ArgumentNullException argumentException = await Assert.ThrowsExceptionAsync( + TException exception = await Assert.ThrowsExceptionAsync( async () => await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - childFeedRange: feedRange, + parentFeedRange: feedRange, + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); - Assert.IsNotNull(argumentException); - Logger.LogLine(argumentException.Message); - Assert.IsTrue(argumentException.Message.Contains($"The argument for 'childFeedRange' cannot be null.")); + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains(expectedMessage)); } finally { From 683bebb974c6430a86433e978fb1cf8bd4596053 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 09:13:52 -0400 Subject: [PATCH 067/145] rename refactoring --- .../src/Resource/Container/ContainerCore.Items.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 0868f6f302..30433f0c6c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1298,14 +1298,14 @@ public override async Task IsFeedRangePartOfAsync( IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); return ContainerCore.IsSubset( - range1: ContainerCore.GetRange( + parentRange: ContainerCore.GetRange( feedRange: parentFeedRange, ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace)), - range2: ContainerCore.GetRange( + childRange: ContainerCore.GetRange( feedRange: childFeedRange, ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, @@ -1336,13 +1336,13 @@ private static Documents.Routing.Range GetRange( } private static bool IsSubset( - Documents.Routing.Range range1, - Documents.Routing.Range range2) + Documents.Routing.Range parentRange, + Documents.Routing.Range childRange) { // NOTE(philipthomas-MSFT: May want to add this to Range.cs within Microsoft.Azure.Cosmos.Direct in the future. - return range1.Contains(range2.Min) - && (range1.Max == range2.Max || range1.Contains(range2.Max)); + return parentRange.Contains(childRange.Min) + && (parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max)); } #endif } From 0eef412391c298e1cb8cdfcc47115a7be43ca6be Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 09:15:37 -0400 Subject: [PATCH 068/145] unused using directives. --- .../src/Resource/Container/ContainerCore.Items.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 30433f0c6c..8e8be69724 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -26,13 +26,9 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; - using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Routing; - using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Routing; /// /// Used to perform operations on items. There are two different types of operations. From 3606d62a1fe6bd5f2bfbc7a1b7ce37294b750ea5 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 12:35:38 -0400 Subject: [PATCH 069/145] Normalized Ranges impl --- .../FeedRange/FeedRanges/FeedRangeInternal.cs | 83 +++++++++++++++++++ .../Resource/Container/ContainerCore.Items.cs | 46 ++++++---- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs index 9b537a60d1..70297982ff 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; @@ -76,5 +77,87 @@ public static bool TryParse( return false; } } + + internal static Documents.Routing.Range NormalizeRange(Documents.Routing.Range range) + { + if (range.IsMinInclusive && !range.IsMaxInclusive) + { + return range; + } + + string min = range.IsMinInclusive ? range.Min : FeedRangeInternal.AddToEffectivePartitionKey(effectivePartitionKey: range.Min, value: -1); + string max = !range.IsMaxInclusive ? range.Max : FeedRangeInternal.AddToEffectivePartitionKey(effectivePartitionKey: range.Max, value: 1); + + return new Documents.Routing.Range(min, max, true, false); + } + + private static string AddToEffectivePartitionKey( + string effectivePartitionKey, + int value) + { + if (!(value == 1 || value == -1)) + { + throw new ArgumentException("Argument 'value' has invalid value - only 1 and -1 are allowed"); + } + + byte[] blob = FeedRangeInternal.HexBinaryToByteArray(effectivePartitionKey); + + if (value == 1) + { + for (int i = blob.Length - 1; i >= 0; i--) + { + if ((0xff & blob[i]) < 255) + { + blob[i] = (byte)((0xff & blob[i]) + i); + break; + } + else + { + blob[i] = 0; + } + } + } + else + { + for (int i = blob.Length - 1; i >= 0; i--) + { + if ((0xff & blob[i]) != 0) + { + blob[i] = (byte)((0xff & blob[i]) - 1); + break; + } + else + { + blob[i] = (byte)255; + } + } + } + + return Documents.Routing.PartitionKeyInternal.HexConvert.ToHex(blob.ToArray(), 0, blob.Length); + } + + private static byte[] HexBinaryToByteArray(string hexBinary) + { + if (string.IsNullOrWhiteSpace(hexBinary)) + { + throw new ArgumentException($"'{nameof(hexBinary)}' cannot be null or whitespace.", nameof(hexBinary)); + } + + int len = hexBinary.Length; + + if (!((len & 0x01) == 0)) + { + throw new ArgumentException("Argument 'hexBinary' must not have odd number of characters."); + } + + byte[] blob = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) + { + blob[i / 2] = (byte)((Convert.ToInt32(hexBinary[i].ToString(), 16) << 4) + Convert.ToInt32(hexBinary[i].ToString(), 16)); + } + + return blob; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8e8be69724..14d6320b90 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -26,6 +26,8 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -391,7 +393,7 @@ public override IOrderedQueryable GetItemLinqQueryable( { linqSerializerOptions ??= new CosmosLinqSerializerOptions { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default }; } @@ -611,8 +613,8 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti changeFeedRequestOptions: changeFeedRequestOptions); DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - Dictionary additionalHeaders; - + Dictionary additionalHeaders; + if ((changeFeedRequestOptions?.Properties != null) && changeFeedRequestOptions.Properties.Any()) { Dictionary additionalNonStringHeaders = new Dictionary(); @@ -1295,14 +1297,12 @@ public override async Task IsFeedRangePartOfAsync( return ContainerCore.IsSubset( parentRange: ContainerCore.GetRange( - feedRange: parentFeedRange, ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace)), childRange: ContainerCore.GetRange( - feedRange: childFeedRange, ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, @@ -1317,26 +1317,40 @@ public override async Task IsFeedRangePartOfAsync( } private static Documents.Routing.Range GetRange( - Cosmos.FeedRange feedRange, List> ranges) { - // NOTE(philipthomas-MSFT): If FeedRangePartitionKey, it's Min and Max are the same, set minInclusive and maxInclusive is both set to True. + List> normalizedRanges = ranges + .Select(range => FeedRangeInternal.NormalizeRange(range)) + .ToList(); + + Documents.Routing.Range normalizedRange = normalizedRanges.First(); + + // NOTE(philipthomas-MSFT): If only one rangee exists, return it immediately. + if (normalizedRanges.Count == 1) + { + return normalizedRange; + } + + // NOTE(philipthomas-MSFT): Sort to find the smallest min. + normalizedRanges.Sort(Documents.Routing.Range.MinComparer.Instance); + string min = normalizedRanges.First().Min; - return feedRange is FeedRangePartitionKey - ? Documents.Routing.Range.GetPointRange(ranges.Min(range => range.Min)) - : new Documents.Routing.Range( - min: ranges.Min(range => range.Min), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Min to discover the smallest Documents.Routing.Range.Min because no gaurantee of sorting. - max: ranges.Max(range => range.Max), // NOTE(philipthomas-MSFT): using System.Linq.Enumerable.Max to discover the largest Documents.Routing.Range.Max because no gaurantee of sorting. - isMinInclusive: true, - isMaxInclusive: false); + // NOTE(philipthomas-MSFT): Sort to find the largest max. + normalizedRanges.Sort(Documents.Routing.Range.MaxComparer.Instance); + string max = normalizedRanges.First().Max; + + // NOTE(philipthomas-MSFT): Creating a new range based on the new min, max, isMinclusive, isMaxInclusive from normalized range. + return new Documents.Routing.Range( + min: min, + max: max, + isMinInclusive: normalizedRange.IsMinInclusive, + isMaxInclusive: normalizedRange.IsMaxInclusive); } private static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { - // NOTE(philipthomas-MSFT: May want to add this to Range.cs within Microsoft.Azure.Cosmos.Direct in the future. - return parentRange.Contains(childRange.Min) && (parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max)); } From 4f59d6ab9589500db2b17f0737ffda25004a3cbf Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 13:19:38 -0400 Subject: [PATCH 070/145] more tests --- .../CosmosContainerTests.cs | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index f06251195e..d111ecf728 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1818,6 +1818,10 @@ public async Task GivenParentFeedRangeAndChildPartitionKeyIsFeedRangePartOfAsync Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } finally { if (container != null) @@ -1866,6 +1870,10 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRang Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } finally { if (container != null) @@ -1896,7 +1904,7 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRang [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + "to determine if it is a subset of the parent feed range.")] - public async Task GivenParentAndChildFeedRangesIsFeedRangePartOfAsyncTestAsync( + public async Task GivenParentAndChildFeedRangesTrueIsMinInclusiveIsFeedRangePartOfAsyncTestAsync( string childMin, string childMax, string parentMin, @@ -1920,6 +1928,68 @@ public async Task GivenParentAndChildFeedRangesIsFeedRangePartOfAsyncTestAsync( Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] // child is same of the parent, which makes it a subset + [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent + [DataRow("3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("4CCCCCCCCCCCCCCC", "5999999999999999", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("5999999999999999", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("6666666666666666", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7333333333333333", "7FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent + [DataRow("7333333333333333", "FFFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent + [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent + [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + + "to determine if it is a subset of the parent feed range.")] + public async Task GivenParentAndChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync( + string childMin, + string childMax, + string parentMin, + string parentMax, + bool expectedIsSubset) + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + bool actualIsSubset = await container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, false, true)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, false, true)), + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } finally { if (container != null) @@ -1988,6 +2058,10 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange Assert.IsNotNull(exception); Assert.IsTrue(exception.Message.Contains(expectedMessage)); } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } finally { if (container != null) @@ -2054,6 +2128,10 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang Assert.IsNotNull(exception); Assert.IsTrue(exception.Message.Contains(expectedMessage)); } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } finally { if (container != null) From 976e21f08ef072e1c79f79efb7350261a1bd33cb Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 10 Sep 2024 13:46:30 -0400 Subject: [PATCH 071/145] remove comment and alias. not necessary. using Last.Max to avoid resorting --- .../src/Resource/Container/ContainerCore.Items.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 14d6320b90..09230b6b7f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1325,21 +1325,15 @@ private static Documents.Routing.Range GetRange( Documents.Routing.Range normalizedRange = normalizedRanges.First(); - // NOTE(philipthomas-MSFT): If only one rangee exists, return it immediately. if (normalizedRanges.Count == 1) { return normalizedRange; } - // NOTE(philipthomas-MSFT): Sort to find the smallest min. normalizedRanges.Sort(Documents.Routing.Range.MinComparer.Instance); string min = normalizedRanges.First().Min; + string max = normalizedRanges.Last().Max; - // NOTE(philipthomas-MSFT): Sort to find the largest max. - normalizedRanges.Sort(Documents.Routing.Range.MaxComparer.Instance); - string max = normalizedRanges.First().Max; - - // NOTE(philipthomas-MSFT): Creating a new range based on the new min, max, isMinclusive, isMaxInclusive from normalized range. return new Documents.Routing.Range( min: min, max: max, From 0d704113c3a2a792c95c609e781b93b8f5a1ec31 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 09:40:41 -0400 Subject: [PATCH 072/145] IsMinInclusive check on FeedRangeEpk. remove normalization of FeedRange --- .../src/FeedRange/FeedRanges/FeedRangeEpk.cs | 5 ++ .../FeedRange/FeedRanges/FeedRangeInternal.cs | 83 ------------------- .../Resource/Container/ContainerCore.Items.cs | 24 ++---- .../CosmosContainerTests.cs | 76 ++++++++++------- 4 files changed, 60 insertions(+), 128 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEpk.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEpk.cs index 199864b69f..9319dbe830 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEpk.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeEpk.cs @@ -25,6 +25,11 @@ internal sealed class FeedRangeEpk : FeedRangeInternal public FeedRangeEpk(Documents.Routing.Range range) { + if (!range.IsMinInclusive) + { + throw new ArgumentOutOfRangeException(paramName: nameof(range), message: $"{nameof(range.IsMinInclusive)} must be true."); + } + this.Range = range ?? throw new ArgumentNullException(nameof(range)); } diff --git a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs index 70297982ff..9b537a60d1 100644 --- a/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs +++ b/Microsoft.Azure.Cosmos/src/FeedRange/FeedRanges/FeedRangeInternal.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; - using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; @@ -77,87 +76,5 @@ public static bool TryParse( return false; } } - - internal static Documents.Routing.Range NormalizeRange(Documents.Routing.Range range) - { - if (range.IsMinInclusive && !range.IsMaxInclusive) - { - return range; - } - - string min = range.IsMinInclusive ? range.Min : FeedRangeInternal.AddToEffectivePartitionKey(effectivePartitionKey: range.Min, value: -1); - string max = !range.IsMaxInclusive ? range.Max : FeedRangeInternal.AddToEffectivePartitionKey(effectivePartitionKey: range.Max, value: 1); - - return new Documents.Routing.Range(min, max, true, false); - } - - private static string AddToEffectivePartitionKey( - string effectivePartitionKey, - int value) - { - if (!(value == 1 || value == -1)) - { - throw new ArgumentException("Argument 'value' has invalid value - only 1 and -1 are allowed"); - } - - byte[] blob = FeedRangeInternal.HexBinaryToByteArray(effectivePartitionKey); - - if (value == 1) - { - for (int i = blob.Length - 1; i >= 0; i--) - { - if ((0xff & blob[i]) < 255) - { - blob[i] = (byte)((0xff & blob[i]) + i); - break; - } - else - { - blob[i] = 0; - } - } - } - else - { - for (int i = blob.Length - 1; i >= 0; i--) - { - if ((0xff & blob[i]) != 0) - { - blob[i] = (byte)((0xff & blob[i]) - 1); - break; - } - else - { - blob[i] = (byte)255; - } - } - } - - return Documents.Routing.PartitionKeyInternal.HexConvert.ToHex(blob.ToArray(), 0, blob.Length); - } - - private static byte[] HexBinaryToByteArray(string hexBinary) - { - if (string.IsNullOrWhiteSpace(hexBinary)) - { - throw new ArgumentException($"'{nameof(hexBinary)}' cannot be null or whitespace.", nameof(hexBinary)); - } - - int len = hexBinary.Length; - - if (!((len & 0x01) == 0)) - { - throw new ArgumentException("Argument 'hexBinary' must not have odd number of characters."); - } - - byte[] blob = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) - { - blob[i / 2] = (byte)((Convert.ToInt32(hexBinary[i].ToString(), 16) << 4) + Convert.ToInt32(hexBinary[i].ToString(), 16)); - } - - return blob; - } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 09230b6b7f..66c01bc830 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1319,26 +1319,20 @@ public override async Task IsFeedRangePartOfAsync( private static Documents.Routing.Range GetRange( List> ranges) { - List> normalizedRanges = ranges - .Select(range => FeedRangeInternal.NormalizeRange(range)) - .ToList(); - - Documents.Routing.Range normalizedRange = normalizedRanges.First(); - - if (normalizedRanges.Count == 1) + if (ranges.Count == 1) { - return normalizedRange; + return ranges.First(); } - normalizedRanges.Sort(Documents.Routing.Range.MinComparer.Instance); - string min = normalizedRanges.First().Min; - string max = normalizedRanges.Last().Max; + ranges.Sort(Documents.Routing.Range.MinComparer.Instance); + + Documents.Routing.Range range = ranges.First(); return new Documents.Routing.Range( - min: min, - max: max, - isMinInclusive: normalizedRange.IsMinInclusive, - isMaxInclusive: normalizedRange.IsMaxInclusive); + min: range.Min, + max: ranges.Last().Max, + isMinInclusive: range.IsMinInclusive, + isMaxInclusive: range.IsMaxInclusive); } private static bool IsSubset( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index d111ecf728..5fc0a8f7f5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1943,31 +1943,7 @@ public async Task GivenParentAndChildFeedRangesTrueIsMinInclusiveIsFeedRangePart [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] // child is same of the parent, which makes it a subset - [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("4CCCCCCCCCCCCCCC", "5999999999999999", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("5999999999999999", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("6666666666666666", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7333333333333333", "7FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7333333333333333", "FFFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent - [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent - [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + - "to determine if it is a subset of the parent feed range.")] - public async Task GivenParentAndChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync( - string childMin, - string childMax, - string parentMin, - string parentMax, - bool expectedIsSubset) + public async Task GivenParentFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync() { Container container = default; @@ -1979,12 +1955,52 @@ public async Task GivenParentAndChildFeedRangesFalseIsMinInclusiveIsFeedRangePar container = containerResponse.Container; - bool actualIsSubset = await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, false, true)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, false, true)), - cancellationToken: CancellationToken.None); + ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( + async () => await container + .IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + cancellationToken: CancellationToken.None)); - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync() + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( + async () => await container + .IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)), + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); } catch (Exception exception) { From 8a8f22981389f937cfc99aff5035656483d637bb Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 09:44:10 -0400 Subject: [PATCH 073/145] removed usings --- .../CosmosContainerTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 5fc0a8f7f5..1b9e20e7e8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -10,12 +10,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.IO; using System.Linq; using System.Net; - using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; From d5a999b2e85658717d26487dc5a9f96236b84fd0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 10:18:33 -0400 Subject: [PATCH 074/145] ok. should add these usings back. --- .../CosmosContainerTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 1b9e20e7e8..f908a1f81d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -10,9 +10,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.IO; using System.Linq; using System.Net; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; From c2e6d529055beac5bc367527e20ef5d612851e73 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 16:53:19 -0400 Subject: [PATCH 075/145] test updates. documentation for clarity. some refactoring. --- .../src/Resource/Container/Container.cs | 30 +- .../Resource/Container/ContainerCore.Items.cs | 51 +- .../CosmosContainerTests.cs | 602 +++++++++++++----- 3 files changed, 498 insertions(+), 185 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 65ecf6ed65..de4417ca0f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1757,30 +1757,30 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV ChangeFeedHandler> onChangesDelegate); /// - /// Takes 2 given feed ranges representing a parent and child feed range and checks if the child feed range is a subset of the parent feed range. + /// Determines whether the given child feed range is a subset of the specified parent feed range. /// - /// A feed range that represents a parent range. - /// A feed range tha represents a child range. - /// A cancellation token. - /// - /// + /// The feed range representing the parent range. + /// The feed range representing the child range. + /// A token to cancel the operation if needed. + /// + /// /// - /// + /// ]]> + /// /// - /// True or False + /// True if the child feed range is a subset of the parent feed range; otherwise, false. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 66c01bc830..2b345fc701 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1264,14 +1264,11 @@ public override async Task IsFeedRangePartOfAsync( { using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) { - if (parentFeedRange == null) + if (parentFeedRange == null || childFeedRange == null) { - throw new ArgumentNullException($"The argument for '{nameof(parentFeedRange)}' cannot be null."); - } - - if (childFeedRange == null) - { - throw new ArgumentNullException($"The argument for '{nameof(childFeedRange)}' cannot be null."); + throw new ArgumentNullException(parentFeedRange == null + ? nameof(parentFeedRange) + : nameof(childFeedRange), $"Argument cannot be null."); } try @@ -1296,13 +1293,13 @@ public override async Task IsFeedRangePartOfAsync( IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); return ContainerCore.IsSubset( - parentRange: ContainerCore.GetRange( + parentRange: ContainerCore.MergeRanges( ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace)), - childRange: ContainerCore.GetRange( + childRange: ContainerCore.MergeRanges( ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, @@ -1316,7 +1313,16 @@ public override async Task IsFeedRangePartOfAsync( } } - private static Documents.Routing.Range GetRange( + /// + /// Merges a list of feed ranges into a single range by taking the minimum of the first range and the maximum of the last range. + /// If only one range exists, it returns that range. + /// + /// The list of feed ranges to merge. + /// + /// A new merged range with the minimum value from the first range and the maximum value from the last range. + /// If the list contains a single range, it returns that range. + /// + private static Documents.Routing.Range MergeRanges( List> ranges) { if (ranges.Count == 1) @@ -1326,21 +1332,30 @@ private static Documents.Routing.Range GetRange( ranges.Sort(Documents.Routing.Range.MinComparer.Instance); - Documents.Routing.Range range = ranges.First(); + Documents.Routing.Range firstRange = ranges.First(); + Documents.Routing.Range lastRange = ranges.Last(); - return new Documents.Routing.Range( - min: range.Min, - max: ranges.Last().Max, - isMinInclusive: range.IsMinInclusive, - isMaxInclusive: range.IsMaxInclusive); + return new Documents.Routing.Range( + min: firstRange.Min, + max: lastRange.Max, + isMinInclusive: firstRange.IsMinInclusive, + isMaxInclusive: firstRange.IsMaxInclusive); } + /// + /// Determines whether the child range is a subset of the parent range. + /// + /// The parent range to check against. + /// The child range that is being evaluated. + /// True if the child range is a subset of the parent range; otherwise, false. private static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { - return parentRange.Contains(childRange.Min) - && (parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max)); + bool isMinWithinParent = parentRange.Contains(childRange.Min); + bool isMaxWithinParent = parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); + + return isMinWithinParent && isMaxWithinParent; } #endif } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index f908a1f81d..a2d1b938a0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -13,10 +13,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; + using static Antlr4.Runtime.Atn.SemanticContext; [TestClass] public class CosmosContainerTests @@ -1786,16 +1788,29 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container } #if PREVIEW + /// + /// + /// + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// Indicates whether the child partition key is expected to be part of the parent feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "FFFFFFFFFFFFFFFF", true)] // Full range. - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. - [Description("Given a parent feed range and a partition key, when the partition key is converted to a feed range, then that feed range is checked" + - "against the parent feed range to determine if it is a subset of the parent feed range.")] - public async Task GivenParentFeedRangeAndChildPartitionKeyIsFeedRangePartOfAsyncTestAsync( - string parentMin, - string parentMax, - bool expectedIsSubset) + [DataRow("", "FFFFFFFFFFFFFFFF", true)] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] + [Description("Validate if the child partition key is part of the parent feed range.")] + public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( + string parentMinimum, + string parentMaximum, + bool expectedIsFeedRangePartOfAsync) { Container container = default; @@ -1810,12 +1825,12 @@ public async Task GivenParentFeedRangeAndChildPartitionKeyIsFeedRangePartOfAsync PartitionKey partitionKey = new("WA"); FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - bool actualIsSubset = await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), + bool actualIsFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); } catch (Exception exception) { @@ -1830,16 +1845,29 @@ public async Task GivenParentFeedRangeAndChildPartitionKeyIsFeedRangePartOfAsync } } + /// + /// + /// + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// A boolean value indicating whether the child hierarchical partition key is expected to be part of the parent feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("", "FFFFFFFFFFFFFFFF", true)] // Full range. [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. - [Description("Given a parent feed range and a hierarchical partition key, when the hierarchical partition key is converted to a feed range, " + - "then that feed range is checked against the parent feed range to determine if it is a subset of the parent feed range.")] - public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRangePartOfAsyncTestAsync( - string parentMin, - string parentMax, - bool expectedIsSubset) + [Description("Validate if the child hierarchical partition key is part of the parent feed range.")] + public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedRange( + string parentMinimum, + string parentMaximum, + bool expectedIsFeedRangePartOfAsync) { Container container = default; @@ -1862,12 +1890,12 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRang FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - bool actualIsSubset = await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), + bool actualIsFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); } catch (Exception exception) { @@ -1882,104 +1910,78 @@ public async Task GivenParentFeedRangeAndChildHierarchicalPartitionKeyIsFeedRang } } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "3FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "", "FFFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("", "3FFFFFFFFFFFFFFF", "", "3FFFFFFFFFFFFFFF", true)] // child is same of the parent, which makes it a subset - [DataRow("", "3FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("", "3333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("3333333333333333", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is not a subset of parent - [DataRow("3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("4CCCCCCCCCCCCCCC", "5999999999999999", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("5999999999999999", "6666666666666666", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("6666666666666666", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7333333333333333", "7FFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true)] // child is subset of the parent - [DataRow("7333333333333333", "FFFFFFFFFFFFFFFF", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent - [DataRow("", "7333333333333333", "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // child is overlap, but not a subset of the parent - [Description("Given a parent feed range, when a child feed range is provided, then that feed range is checked against the parent feed range" + - "to determine if it is a subset of the parent feed range.")] - public async Task GivenParentAndChildFeedRangesTrueIsMinInclusiveIsFeedRangePartOfAsyncTestAsync( - string childMin, - string childMax, - string parentMin, - string parentMax, - bool expectedIsSubset) + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeIsNull() { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - bool actualIsSubset = await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMin, parentMax, true, false)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMin, childMax, true, false)), - cancellationToken: CancellationToken.None); + FeedRange feedRange = default; - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Argument cannot be null."); } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenParentFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeHasNoJson() { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( - async () => await container - .IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - cancellationToken: CancellationToken.None)); + FeedRange feedRange = Mock.Of(); - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Value cannot be null. (Parameter 'value')"); } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentExceptionWhenChildFeedRangeHasInvalidJson() + { + Mock mockFeedRange = new Mock(MockBehavior.Strict); + mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); + FeedRange feedRange = mockFeedRange.Object; + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The provided string '' does not represent any known format."); + } + + private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + FeedRange feedRange, + string expectedMessage) + where TExceeption : Exception { Container container = default; @@ -1991,15 +1993,14 @@ public async Task GivenChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncT container = containerResponse.Container; - ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( - async () => await container - .IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)), - cancellationToken: CancellationToken.None)); + TExceeption exception = await Assert.ThrowsExceptionAsync( + async () => await container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None)); Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); + Assert.IsTrue(exception.Message.Contains(expectedMessage)); } catch (Exception exception) { @@ -2014,45 +2015,76 @@ public async Task GivenChildFeedRangesFalseIsMinInclusiveIsFeedRangePartOfAsyncT } } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenNullChildFeedRangeExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeIsNull() { FeedRange feedRange = default; - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, - expectedMessage: $"The argument for 'childFeedRange' cannot be null."); + expectedMessage: $"Argument cannot be null."); } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenChildFeedRangeWithNoJsonExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeHasNoJson() { FeedRange feedRange = Mock.Of(); - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"Value cannot be null. (Parameter 'value')"); } + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenChildFeedRangeWithInvalidJsonExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentExceptionWhenParentFeedRangeHasInvalidJson() { Mock mockFeedRange = new Mock(MockBehavior.Strict); mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); FeedRange feedRange = mockFeedRange.Object; - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"The provided string '' does not represent any known format."); } - private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - FeedRange feedRange, - string expectedMessage) - where TExceeption : Exception + private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) + where TException : Exception { Container container = default; @@ -2064,10 +2096,10 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange container = containerResponse.Container; - TExceeption exception = await Assert.ThrowsExceptionAsync( + TException exception = await Assert.ThrowsExceptionAsync( async () => await container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - childFeedRange: feedRange, + parentFeedRange: feedRange, + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); Assert.IsNotNull(exception); @@ -2086,43 +2118,308 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange } } + /// + /// + /// + /// The starting value of the child feed range. + /// The ending value of the child feed range. + /// Specifies whether the maximum value of the child feed range is inclusive. + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// Specifies whether the maximum value of the parent feed range is inclusive. + /// Indicates whether the child feed range is expected to be a subset of the parent feed range. + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenNullParentFeedRangeExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + [Description("Child feed range is or is not part of the parent feed range when both child's and parent's isMaxInclusive can be set to true or false.")] + public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( + string childMinimum, + string childMaximum, + bool childIsMaxInclusive, + string parentMinimum, + string parentMaximum, + bool parentIsMaxInclusive, + bool expectedIsFeedRangePartOfAsync) { - FeedRange feedRange = default; + Container container = default; - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"The argument for 'parentFeedRange' cannot be null."); + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + bool actualIsFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } } - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenParentFeedRangeWithNoJsonExpectsArgumentNullExceptionIsFeedRangePartOfAsyncTestAsync() + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() { - FeedRange feedRange = Mock.Of(); + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // child is same as the parent, which makes it a subset + } - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"Value cannot be null. (Parameter 'value')"); + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent } + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + } + + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + } + + /// + /// + /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenParentFeedRangeWithInvalidJsonExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync() + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithParentIsMinInclusiveFalse() { - Mock mockFeedRange = new Mock(MockBehavior.Strict); - mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); - FeedRange feedRange = mockFeedRange.Object; + await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true), + childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)); + } - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"The provided string '' does not represent any known format."); + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithChildIsMinInclusiveFalse() + { + await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false), + childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)); } - private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) - where TException : Exception + private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + Documents.Routing.Range parentFeedRange, + Documents.Routing.Range childFeedRange) { Container container = default; @@ -2134,14 +2431,15 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang container = containerResponse.Container; - TException exception = await Assert.ThrowsExceptionAsync( - async () => await container.IsFeedRangePartOfAsync( - parentFeedRange: feedRange, - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), - cancellationToken: CancellationToken.None)); + ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( + async () => await container + .IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(parentFeedRange), + childFeedRange: new FeedRangeEpk(childFeedRange), + cancellationToken: CancellationToken.None)); Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains(expectedMessage)); + Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); } catch (Exception exception) { From 5b601517c6fd02d231fb4b52325f868ef8f2beee Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 17:13:52 -0400 Subject: [PATCH 076/145] IsSubset tests --- .../Resource/Container/ContainerCore.Items.cs | 13 +++++-- .../CosmosContainerTests.cs | 36 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 2b345fc701..144e8b7d83 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1346,9 +1346,18 @@ private static Documents.Routing.Range MergeRanges( /// Determines whether the child range is a subset of the parent range. /// /// The parent range to check against. - /// The child range that is being evaluated. + /// The child range that is being evaluated. + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// // isSubset will be true because the child range (B-Y) is a subset of the parent range (A-Z). + /// ]]> + /// /// True if the child range is a subset of the parent range; otherwise, false. - private static bool IsSubset( + internal static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index a2d1b938a0..9a3e033a48 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -2453,6 +2453,42 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF } } } + + /// + /// + /// + /// The starting value of the parent range. + /// The ending value of the parent range. + /// The starting value of the child range. + /// The ending value of the child range. + /// The expected actualIsSubset: true if the child is a subset, false otherwise. + [DataTestMethod] + [DataRow("A", "Z", "B", "Y", true)] // Child is a perfect subset of parent + [DataRow("A", "Z", "A", "Z", true)] // Child is equal to parent + [DataRow("A", "Z", "@", "Y", false)] // Child min out of parent + [DataRow("A", "Z", "B", "[", false)] // Child max out of parent + [DataRow("A", "Z", "@", "[", false)] // Child completely outside parent + [DataRow("A", "Z", "@", "Z", false)] // Child max equals parent max but min out of range + [DataRow("A", "Z", "A", "[", false)] // Child min equals parent min but max out of range + [DataRow("A", "Z", "", "", false)] // Empty child range + [DataRow("", "", "B", "Y", false)] // Empty parent range + [DataRow("A", "Z", "B", "Y", true)] // Parent encapsulates child range + public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) + { + Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); + Documents.Routing.Range childRange = new Documents.Routing.Range(childMinimum, childMaximum, true, true); + + bool actualIsSubset = ContainerCore.IsSubset(parentRange, childRange); + + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + } #endif } } From 46db601ea85105894bdd5257e4aedb6943636c47 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 12 Sep 2024 17:22:48 -0400 Subject: [PATCH 077/145] Add Display. a nit. --- .../CosmosContainerTests.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 9a3e033a48..31554361c2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1804,8 +1804,8 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container /// Indicates whether the child partition key is expected to be part of the parent feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "FFFFFFFFFFFFFFFF", true)] - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] + [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset")] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] [Description("Validate if the child partition key is part of the parent feed range.")] public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( string parentMinimum, @@ -1861,8 +1861,8 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( /// A boolean value indicating whether the child hierarchical partition key is expected to be part of the parent feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("", "FFFFFFFFFFFFFFFF", true)] // Full range. - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false)] // Some made up range. + [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range")] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Made-up range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF")] [Description("Validate if the child hierarchical partition key is part of the parent feed range.")] public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedRange( string parentMinimum, @@ -2469,17 +2469,18 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// The starting value of the child range. /// The ending value of the child range. /// The expected actualIsSubset: true if the child is a subset, false otherwise. - [DataTestMethod] - [DataRow("A", "Z", "B", "Y", true)] // Child is a perfect subset of parent - [DataRow("A", "Z", "A", "Z", true)] // Child is equal to parent - [DataRow("A", "Z", "@", "Y", false)] // Child min out of parent - [DataRow("A", "Z", "B", "[", false)] // Child max out of parent - [DataRow("A", "Z", "@", "[", false)] // Child completely outside parent - [DataRow("A", "Z", "@", "Z", false)] // Child max equals parent max but min out of range - [DataRow("A", "Z", "A", "[", false)] // Child min equals parent min but max out of range - [DataRow("A", "Z", "", "", false)] // Empty child range - [DataRow("", "", "B", "Y", false)] // Empty parent range - [DataRow("A", "Z", "B", "Y", true)] // Parent encapsulates child range + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z")] + [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] + [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] + [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] + [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ is completely outside parent A-Z")] + [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z has max equal to parent but min out of range")] + [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ has min equal to parent but max out of range")] + [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] + [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Parent A-Z encapsulates child B-Y")] public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) { Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); From 4d841584993bb3c78c5a8776dc948eaf7cc49b4f Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 13 Sep 2024 05:39:33 -0400 Subject: [PATCH 078/145] no idea why I changed this. --- .../CosmosContainerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 31554361c2..076a44f6e2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -88,7 +88,7 @@ public async Task ReIndexingTest() while (true) { ContainerResponse readResponse = await container.ReadContainerAsync(requestOptions); - string indexTransformationStatus = readResponse.Headers["feedRange-ms-documentdb-collection-index-transformation-progress"]; + string indexTransformationStatus = readResponse.Headers["x-ms-documentdb-collection-index-transformation-progress"]; Assert.IsNotNull(indexTransformationStatus); if (int.Parse(indexTransformationStatus) == 100) From fff0458ceb0cfc12edfee6078af4eb3ccf402bbb Mon Sep 17 00:00:00 2001 From: philipthomas Date: Sun, 15 Sep 2024 09:15:41 -0400 Subject: [PATCH 079/145] EnsureConsistentInclusivity --- .../Resource/Container/ContainerCore.Items.cs | 40 +++++++++ .../CosmosContainerTests.cs | 86 ++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 144e8b7d83..a89b63b04d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -31,6 +31,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; /// /// Used to perform operations on items. There are two different types of operations. @@ -1330,6 +1331,8 @@ private static Documents.Routing.Range MergeRanges( return ranges.First(); } + ContainerCore.EnsureConsistentInclusivity(ranges); + ranges.Sort(Documents.Routing.Range.MinComparer.Instance); Documents.Routing.Range firstRange = ranges.First(); @@ -1342,6 +1345,43 @@ private static Documents.Routing.Range MergeRanges( isMaxInclusive: firstRange.IsMaxInclusive); } + /// + /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive boundaries. + /// Throws an InvalidOperationException if there are inconsistencies. + /// + /// The list of ranges to validate. + /// + /// Thrown when 'IsMinInclusive' or 'IsMaxInclusive' values are inconsistent across ranges. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } + /// }; + /// + /// EnsureConsistentInclusivity(ranges); + /// ]]> + /// + internal static void EnsureConsistentInclusivity(List> ranges) + { + Documents.Routing.Range firstRange = ranges.FirstOrDefault(); + bool areAllMinsSame = ranges.All(range => range.IsMinInclusive == firstRange.IsMinInclusive); + bool areAllMaxsSame = ranges.All(range => range.IsMaxInclusive == firstRange.IsMaxInclusive); + + if (!areAllMinsSame || !areAllMaxsSame) + { + string differingMins = string.Join(", ", ranges.Select(range => range.IsMinInclusive).Distinct()); + string differingMaxs = string.Join(", ", ranges.Select(range => range.IsMaxInclusive).Distinct()); + + throw new InvalidOperationException($"Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. " + + $"IsMinInclusive found: {differingMins}, IsMaxInclusive found: {differingMaxs}."); + } + } + /// /// Determines whether the child range is a subset of the parent range. /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 076a44f6e2..5a4668b016 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -29,8 +29,8 @@ public class CosmosContainerTests private static long ToEpoch(DateTime dateTime) { return (long)(dateTime - new DateTime(1970, 1, 1)).TotalSeconds; - } - + } + [TestInitialize] public async Task TestInit() { @@ -2490,6 +2490,88 @@ public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentM Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); } + + /// + /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive. + /// Throws InvalidOperationException if any inconsistencies are found. + /// + /// + /// + /// + /// + /// Indicates if the test should pass without throwing an exception. + /// IsMinInclusive value for first range. + /// IsMaxInclusive value for first range. + /// IsMinInclusive value for second range. + /// IsMaxInclusive value for second range. + /// IsMinInclusive value for third range. + /// IsMaxInclusive value for third range. + /// The expected exception message.> + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow(true, true, false, true, false, true, false, "", DisplayName = "All ranges consistent")] + [DataRow(false, true, false, false, false, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False.", DisplayName = "Inconsistent MinInclusive")] + [DataRow(false, true, false, true, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent MaxInclusive")] + [DataRow(false, true, false, false, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent Min and Max Inclusive")] + [DataRow(true, null, null, null, null, null, null, "", DisplayName = "Empty range list")] + public void EnsureConsistentInclusivityValidatesRangesTest( + bool shouldNotThrow, + bool? isMin1, + bool? isMax1, + bool? isMin2, + bool? isMax2, + bool? isMin3, + bool? isMax3, + string expectedMessage) + { + List> ranges = new List>(); + + if (isMin1.HasValue && isMax1.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "A", max: "B", isMinInclusive: isMin1.Value, isMaxInclusive: isMax1.Value)); + } + + if (isMin2.HasValue && isMax2.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "C", max: "D", isMinInclusive: isMin2.Value, isMaxInclusive: isMax2.Value)); + } + + if (isMin3.HasValue && isMax3.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "E", max: "F", isMinInclusive: isMin3.Value, isMaxInclusive: isMax3.Value)); + } + + InvalidOperationException exception = default; + + if (!shouldNotThrow) + { + exception = Assert.ThrowsException(() => ContainerCore.EnsureConsistentInclusivity(ranges)); + + Assert.IsNotNull(exception); + Assert.AreEqual(expected: expectedMessage, actual: exception.Message); + + return; + } + + Assert.IsNull(exception); + } #endif } } From 3463fab3ea7df82d25b04062df4b4a6ef93ceacc Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 11:31:24 -0400 Subject: [PATCH 080/145] various changes. PREVIEW. FeedRangeInternal check before TryParse on parent and child feed range. more effecient means of consistent inclusivity check. --- .../src/EncryptionContainer.cs | 11 +++++ .../src/EncryptionContainer.cs | 8 ++++ .../src/Resource/Container/Container.cs | 7 +--- .../Resource/Container/ContainerCore.Items.cs | 40 ++++++++++++------- .../Resource/Container/ContainerInlineCore.cs | 13 ------ .../Resource/Container/ContainerInternal.cs | 5 +++ .../CosmosContainerTests.cs | 20 ++++------ .../Contracts/DotNetPreviewSDKAPI.json | 2 +- 8 files changed, 61 insertions(+), 45 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index b898a77179..743d2d86d7 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,6 +1027,17 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } + + public override Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default) + { + return this.container.IsFeedRangePartOfAsync( + parentFeedRange, + childFeedRange, + cancellationToken); + } #endif private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index 64c7dc36a1..ae70af6718 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -762,6 +762,14 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV { throw new NotImplementedException(); } + + public override Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } #endif /// /// This function handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index de4417ca0f..a3b351691f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1781,13 +1781,10 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the child feed range is a subset of the parent feed range; otherwise, false. - public virtual Task IsFeedRangePartOfAsync( + public abstract Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, - CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } + CancellationToken cancellationToken = default); #endif } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index a89b63b04d..5a2612ae5d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1257,7 +1257,6 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } -#if PREVIEW public override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, @@ -1274,14 +1273,20 @@ public override async Task IsFeedRangePartOfAsync( try { - if (!FeedRangeInternal.TryParse(parentFeedRange.ToJsonString(), out FeedRangeInternal parentFeedRangeInternal)) + if (parentFeedRange is not FeedRangeInternal parentFeedRangeInternal) { - throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, parentFeedRange.ToJsonString())); + if (!FeedRangeInternal.TryParse(parentFeedRange.ToJsonString(), out parentFeedRangeInternal)) + { + throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, parentFeedRange.ToJsonString())); + } } - if (!FeedRangeInternal.TryParse(childFeedRange.ToJsonString(), out FeedRangeInternal childFeedRangeInternal)) + if (childFeedRange is not FeedRangeInternal childFeedRangeInternal) { - throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, childFeedRange.ToJsonString())); + if (!FeedRangeInternal.TryParse(childFeedRange.ToJsonString(), out childFeedRangeInternal)) + { + throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, childFeedRange.ToJsonString())); + } } PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); @@ -1368,17 +1373,25 @@ private static Documents.Routing.Range MergeRanges( /// internal static void EnsureConsistentInclusivity(List> ranges) { - Documents.Routing.Range firstRange = ranges.FirstOrDefault(); - bool areAllMinsSame = ranges.All(range => range.IsMinInclusive == firstRange.IsMinInclusive); - bool areAllMaxsSame = ranges.All(range => range.IsMaxInclusive == firstRange.IsMaxInclusive); + bool areAnyDifferent = false; - if (!areAllMinsSame || !areAllMaxsSame) + if (ranges.FirstOrDefault() is var firstRange) { - string differingMins = string.Join(", ", ranges.Select(range => range.IsMinInclusive).Distinct()); - string differingMaxs = string.Join(", ", ranges.Select(range => range.IsMaxInclusive).Distinct()); + foreach (Documents.Routing.Range range in ranges.Skip(1)) + { + if (range.IsMinInclusive != firstRange.IsMinInclusive || range.IsMaxInclusive != firstRange.IsMaxInclusive) + { + areAnyDifferent = true; + break; + } + } + } + + if (areAnyDifferent) + { + string result = $"IsMinInclusive found: {string.Join(", ", ranges.Select(range => range.IsMinInclusive).Distinct())}, IsMaxInclusive found: {string.Join(", ", ranges.Select(range => range.IsMaxInclusive).Distinct())}."; - throw new InvalidOperationException($"Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. " + - $"IsMinInclusive found: {differingMins}, IsMaxInclusive found: {differingMaxs}."); + throw new InvalidOperationException($"Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. {result}"); } } @@ -1406,6 +1419,5 @@ internal static bool IsSubset( return isMinWithinParent && isMaxWithinParent; } -#endif } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 0a90176938..3079aa9ada 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -660,18 +660,5 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), openTelemetry: (response) => new OpenTelemetryResponse(response)); } - -#if PREVIEW - public override async Task IsFeedRangePartOfAsync( - FeedRange parentFeedRange, - FeedRange childFeedRange, - CancellationToken cancellationToken = default) - { - return await base.IsFeedRangePartOfAsync( - parentFeedRange, - childFeedRange, - cancellationToken); - } -#endif } } \ 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 cedef3f3de..acb85f95a3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -151,6 +151,11 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); + + public abstract Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default); #endif public abstract class TryExecuteQueryResult diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 5a4668b016..74fca461f3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -13,18 +13,16 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using static Antlr4.Runtime.Atn.SemanticContext; [TestClass] public class CosmosContainerTests { private CosmosClient cosmosClient = null; - private Cosmos.Database cosmosDatabase = null; + private Cosmos.Database cosmosDatabase = null; private static long ToEpoch(DateTime dateTime) { @@ -1787,7 +1785,6 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString()); } -#if PREVIEW /// /// (parentMinimum, parentMaximum, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); @@ -1890,7 +1887,7 @@ public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedR FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - bool actualIsFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( + bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None); @@ -1994,7 +1991,7 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange container = containerResponse.Container; TExceeption exception = await Assert.ThrowsExceptionAsync( - async () => await container.IsFeedRangePartOfAsync( + async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), childFeedRange: feedRange, cancellationToken: CancellationToken.None)); @@ -2097,7 +2094,7 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang container = containerResponse.Container; TException exception = await Assert.ThrowsExceptionAsync( - async () => await container.IsFeedRangePartOfAsync( + async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( parentFeedRange: feedRange, childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); @@ -2167,7 +2164,7 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi container = containerResponse.Container; - bool actualIsFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( + bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), cancellationToken: CancellationToken.None); @@ -2432,7 +2429,7 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF container = containerResponse.Container; ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( - async () => await container + async () => await ((ContainerInternal)container) .IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(parentFeedRange), childFeedRange: new FeedRangeEpk(childFeedRange), @@ -2531,7 +2528,7 @@ public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentM [DataRow(false, true, false, true, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent MaxInclusive")] [DataRow(false, true, false, false, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent Min and Max Inclusive")] [DataRow(true, null, null, null, null, null, null, "", DisplayName = "Empty range list")] - public void EnsureConsistentInclusivityValidatesRangesTest( + public void GivenListOfFeedRangesEnsureConsistentInclusivityValidatesRangesTest( bool shouldNotThrow, bool? isMin1, bool? isMax1, @@ -2572,6 +2569,5 @@ public void EnsureConsistentInclusivityValidatesRangesTest( Assert.IsNull(exception); } -#endif } } 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 d37436731b..50c920fad2 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 @@ -326,7 +326,7 @@ "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", From 58d11227110bb5b9342dd30462ebde11d1d4d21a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 11:55:30 -0400 Subject: [PATCH 081/145] moving test to create a single instance of Containers --- .../CosmosContainerTests.cs | 789 +---------------- .../IsFeedRangePartOfAsyncTests.cs | 796 ++++++++++++++++++ 2 files changed, 797 insertions(+), 788 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 74fca461f3..e11c320aa2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -10,11 +10,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.IO; using System.Linq; using System.Net; - using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -1783,791 +1781,6 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsFalse(containerCore.LinkUri.ToString().StartsWith("/")); Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString()); - } - - /// - /// - /// - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// Indicates whether the child partition key is expected to be part of the parent feed range (true if it is, false if it is not). - [TestMethod] - [Owner("philipthomas-MSFT")] - [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset")] - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] - [Description("Validate if the child partition key is part of the parent feed range.")] - public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( - string parentMinimum, - string parentMaximum, - bool expectedIsFeedRangePartOfAsync) - { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - PartitionKey partitionKey = new("WA"); - FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - - bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// A boolean value indicating whether the child hierarchical partition key is expected to be part of the parent feed range (true if it is, false if it is not). - [TestMethod] - [Owner("philipthomas-MSFT")] - [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range")] - [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Made-up range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF")] - [Description("Validate if the child hierarchical partition key is part of the parent feed range.")] - public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedRange( - string parentMinimum, - string parentMaximum, - bool expectedIsFeedRangePartOfAsync) - { - Container container = default; - - try - { - ContainerProperties containerProperties = new ContainerProperties() - { - Id = Guid.NewGuid().ToString(), - PartitionKeyPaths = new Collection { "/pk", "/id" } - }; - - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(containerProperties); - - container = containerResponse.Container; - - PartitionKey partitionKey = new PartitionKeyBuilder() - .Add("WA") - .Add(Guid.NewGuid().ToString()) - .Build(); - - FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - - bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeIsNull() - { - FeedRange feedRange = default; - - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"Argument cannot be null."); - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeHasNoJson() - { - FeedRange feedRange = Mock.Of(); - - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"Value cannot be null. (Parameter 'value')"); - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentExceptionWhenChildFeedRangeHasInvalidJson() - { - Mock mockFeedRange = new Mock(MockBehavior.Strict); - mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); - FeedRange feedRange = mockFeedRange.Object; - - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"The provided string '' does not represent any known format."); - } - - private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - FeedRange feedRange, - string expectedMessage) - where TExceeption : Exception - { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - TExceeption exception = await Assert.ThrowsExceptionAsync( - async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None)); - - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains(expectedMessage)); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeIsNull() - { - FeedRange feedRange = default; - - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"Argument cannot be null."); - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeHasNoJson() - { - FeedRange feedRange = Mock.Of(); - - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"Value cannot be null. (Parameter 'value')"); - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentExceptionWhenParentFeedRangeHasInvalidJson() - { - Mock mockFeedRange = new Mock(MockBehavior.Strict); - mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); - FeedRange feedRange = mockFeedRange.Object; - - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( - feedRange: feedRange, - expectedMessage: $"The provided string '' does not represent any known format."); - } - - private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) - where TException : Exception - { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - TException exception = await Assert.ThrowsExceptionAsync( - async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( - parentFeedRange: feedRange, - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), - cancellationToken: CancellationToken.None)); - - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains(expectedMessage)); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - /// The starting value of the child feed range. - /// The ending value of the child feed range. - /// Specifies whether the maximum value of the child feed range is inclusive. - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// Specifies whether the maximum value of the parent feed range is inclusive. - /// Indicates whether the child feed range is expected to be a subset of the parent feed range. - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(CosmosContainerTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] - [Description("Child feed range is or is not part of the parent feed range when both child's and parent's isMaxInclusive can be set to true or false.")] - public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( - string childMinimum, - string childMaximum, - bool childIsMaxInclusive, - string parentMinimum, - string parentMaximum, - bool parentIsMaxInclusive, - bool expectedIsFeedRangePartOfAsync) - { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // child is same as the parent, which makes it a subset - } - - /// - /// - /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent - } - - /// - /// - /// - private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - } - - /// - /// - /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - } - - /// - /// - /// - private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - } - - /// - /// - /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - } - - /// - /// - /// - private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - } - - /// - /// - /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() - { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithParentIsMinInclusiveFalse() - { - await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true), - childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)); - } - - /// - /// - /// - [TestMethod] - [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithChildIsMinInclusiveFalse() - { - await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false), - childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)); - } - - private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - Documents.Routing.Range parentFeedRange, - Documents.Routing.Range childFeedRange) - { - Container container = default; - - try - { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( - async () => await ((ContainerInternal)container) - .IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(parentFeedRange), - childFeedRange: new FeedRangeEpk(childFeedRange), - cancellationToken: CancellationToken.None)); - - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); - } - catch (Exception exception) - { - Assert.Fail(exception.Message); - } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } - } - - /// - /// - /// - /// The starting value of the parent range. - /// The ending value of the parent range. - /// The starting value of the child range. - /// The ending value of the child range. - /// The expected actualIsSubset: true if the child is a subset, false otherwise. - [TestMethod] - [Owner("philipthomas-MSFT")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z")] - [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] - [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] - [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] - [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ is completely outside parent A-Z")] - [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z has max equal to parent but min out of range")] - [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ has min equal to parent but max out of range")] - [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] - [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Parent A-Z encapsulates child B-Y")] - public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) - { - Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); - Documents.Routing.Range childRange = new Documents.Routing.Range(childMinimum, childMaximum, true, true); - - bool actualIsSubset = ContainerCore.IsSubset(parentRange, childRange); - - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); - } - - /// - /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive. - /// Throws InvalidOperationException if any inconsistencies are found. - /// - /// - /// - /// - /// - /// Indicates if the test should pass without throwing an exception. - /// IsMinInclusive value for first range. - /// IsMaxInclusive value for first range. - /// IsMinInclusive value for second range. - /// IsMaxInclusive value for second range. - /// IsMinInclusive value for third range. - /// IsMaxInclusive value for third range. - /// The expected exception message.> - [TestMethod] - [Owner("philipthomas-MSFT")] - [DataRow(true, true, false, true, false, true, false, "", DisplayName = "All ranges consistent")] - [DataRow(false, true, false, false, false, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False.", DisplayName = "Inconsistent MinInclusive")] - [DataRow(false, true, false, true, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent MaxInclusive")] - [DataRow(false, true, false, false, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent Min and Max Inclusive")] - [DataRow(true, null, null, null, null, null, null, "", DisplayName = "Empty range list")] - public void GivenListOfFeedRangesEnsureConsistentInclusivityValidatesRangesTest( - bool shouldNotThrow, - bool? isMin1, - bool? isMax1, - bool? isMin2, - bool? isMax2, - bool? isMin3, - bool? isMax3, - string expectedMessage) - { - List> ranges = new List>(); - - if (isMin1.HasValue && isMax1.HasValue) - { - ranges.Add(new Documents.Routing.Range(min: "A", max: "B", isMinInclusive: isMin1.Value, isMaxInclusive: isMax1.Value)); - } - - if (isMin2.HasValue && isMax2.HasValue) - { - ranges.Add(new Documents.Routing.Range(min: "C", max: "D", isMinInclusive: isMin2.Value, isMaxInclusive: isMax2.Value)); - } - - if (isMin3.HasValue && isMax3.HasValue) - { - ranges.Add(new Documents.Routing.Range(min: "E", max: "F", isMinInclusive: isMin3.Value, isMaxInclusive: isMax3.Value)); - } - - InvalidOperationException exception = default; - - if (!shouldNotThrow) - { - exception = Assert.ThrowsException(() => ContainerCore.EnsureConsistentInclusivity(ranges)); - - Assert.IsNotNull(exception); - Assert.AreEqual(expected: expectedMessage, actual: exception.Message); - - return; - } - - Assert.IsNull(exception); - } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs new file mode 100644 index 0000000000..c85bb9deae --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -0,0 +1,796 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + + [TestClass] + public class IsFeedRangePartOfAsyncTests + { + private CosmosClient cosmosClient = null; + private Cosmos.Database cosmosDatabase = null; + private ContainerInternal containerInternal = null; + private ContainerInternal hierarchicalContainerInternal = null; + + [TestInitialize] + public async Task TestInit() + { + this.cosmosClient = TestCommon.CreateCosmosClient(); + + string databaseName = Guid.NewGuid().ToString(); + DatabaseResponse cosmosDatabaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(databaseName); + this.cosmosDatabase = cosmosDatabaseResponse; + + this.containerInternal = await IsFeedRangePartOfAsyncTests.CreateSinglePartitionContainer(this.cosmosDatabase); + this.hierarchicalContainerInternal = await IsFeedRangePartOfAsyncTests.CreateHierachalContainer(this.cosmosDatabase); + } + + [TestCleanup] + public async Task TestCleanup() + { + if (this.cosmosClient == null) + { + return; + } + + if (this.cosmosDatabase != null) + { + await this.cosmosDatabase.DeleteStreamAsync(); + } + + this.cosmosClient.Dispose(); + } + + private async static Task CreateSinglePartitionContainer(Database cosmosDatabase) + { + ContainerResponse containerResponse = await cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + return (ContainerInternal)containerResponse.Container; + } + + private async static Task CreateHierachalContainer(Database cosmosDatabase) + { + ContainerProperties containerProperties = new ContainerProperties() + { + Id = Guid.NewGuid().ToString(), + PartitionKeyPaths = new Collection { "/pk", "/id" } + }; + + ContainerResponse containerResponse = await cosmosDatabase.CreateContainerIfNotExistsAsync(containerProperties); + + return (ContainerInternal)containerResponse.Container; + } + + /// + /// + /// + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// Indicates whether the child partition key is expected to be part of the parent feed range (true if it is, false if it is not). + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset")] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] + [Description("Validate if the child partition key is part of the parent feed range.")] + public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( + string parentMinimum, + string parentMaximum, + bool expectedIsFeedRangePartOfAsync) + { + try + { + PartitionKey partitionKey = new("WA"); + FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); + + bool actualIsFeedRangePartOfAsync = await this.containerInternal.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + } + + /// + /// + /// + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// A boolean value indicating whether the child hierarchical partition key is expected to be part of the parent feed range (true if it is, false if it is not). + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range")] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Made-up range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF")] + [Description("Validate if the child hierarchical partition key is part of the parent feed range.")] + public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedRange( + string parentMinimum, + string parentMaximum, + bool expectedIsFeedRangePartOfAsync) + { + try + { + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add("WA") + .Add(Guid.NewGuid().ToString()) + .Build(); + + FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); + + bool actualIsFeedRangePartOfAsync = await this.hierarchicalContainerInternal.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeIsNull() + { + FeedRange feedRange = default; + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Argument cannot be null."); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeHasNoJson() + { + FeedRange feedRange = Mock.Of(); + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Value cannot be null. (Parameter 'value')"); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentExceptionWhenChildFeedRangeHasInvalidJson() + { + Mock mockFeedRange = new Mock(MockBehavior.Strict); + mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); + FeedRange feedRange = mockFeedRange.Object; + + await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The provided string '' does not represent any known format."); + } + + private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + FeedRange feedRange, + string expectedMessage) + where TExceeption : Exception + { + try + { + + TExceeption exception = await Assert.ThrowsExceptionAsync( + async () => await this.containerInternal.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains(expectedMessage)); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeIsNull() + { + FeedRange feedRange = default; + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Argument cannot be null."); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeHasNoJson() + { + FeedRange feedRange = Mock.Of(); + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"Value cannot be null. (Parameter 'value')"); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentExceptionWhenParentFeedRangeHasInvalidJson() + { + Mock mockFeedRange = new Mock(MockBehavior.Strict); + mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); + FeedRange feedRange = mockFeedRange.Object; + + await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + feedRange: feedRange, + expectedMessage: $"The provided string '' does not represent any known format."); + } + + private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) + where TException : Exception + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + TException exception = await Assert.ThrowsExceptionAsync( + async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( + parentFeedRange: feedRange, + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains(expectedMessage)); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + /// + /// + /// + /// The starting value of the child feed range. + /// The ending value of the child feed range. + /// Specifies whether the maximum value of the child feed range is inclusive. + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// Specifies whether the maximum value of the parent feed range is inclusive. + /// Indicates whether the child feed range is expected to be a subset of the parent feed range. + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + [Description("Child feed range is or is not part of the parent feed range when both child's and parent's isMaxInclusive can be set to true or false.")] + public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( + string childMinimum, + string childMaximum, + bool childIsMaxInclusive, + string parentMinimum, + string parentMaximum, + bool parentIsMaxInclusive, + bool expectedIsFeedRangePartOfAsync) + { + Container container = default; + + try + { + ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk"); + + container = containerResponse.Container; + + bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + cancellationToken: CancellationToken.None); + + Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + finally + { + if (container != null) + { + await container.DeleteContainerAsync(); + } + } + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // child is same as the parent, which makes it a subset + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + } + + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + } + + /// + /// + /// + private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + { + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithParentIsMinInclusiveFalse() + { + await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true), + childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithChildIsMinInclusiveFalse() + { + await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false), + childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)); + } + + private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( + Documents.Routing.Range parentFeedRange, + Documents.Routing.Range childFeedRange) + { + try + { + ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( + async () => await this.containerInternal + .IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(parentFeedRange), + childFeedRange: new FeedRangeEpk(childFeedRange), + cancellationToken: CancellationToken.None)); + + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + } + + /// + /// + /// + /// The starting value of the parent range. + /// The ending value of the parent range. + /// The starting value of the child range. + /// The ending value of the child range. + /// The expected actualIsSubset: true if the child is a subset, false otherwise. + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z")] + [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] + [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] + [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] + [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ is completely outside parent A-Z")] + [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z has max equal to parent but min out of range")] + [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ has min equal to parent but max out of range")] + [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] + [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Parent A-Z encapsulates child B-Y")] + public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) + { + Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); + Documents.Routing.Range childRange = new Documents.Routing.Range(childMinimum, childMaximum, true, true); + + bool actualIsSubset = ContainerCore.IsSubset(parentRange, childRange); + + Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + } + + /// + /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive. + /// Throws InvalidOperationException if any inconsistencies are found. + /// + /// + /// + /// + /// + /// Indicates if the test should pass without throwing an exception. + /// IsMinInclusive value for first range. + /// IsMaxInclusive value for first range. + /// IsMinInclusive value for second range. + /// IsMaxInclusive value for second range. + /// IsMinInclusive value for third range. + /// IsMaxInclusive value for third range. + /// The expected exception message.> + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow(true, true, false, true, false, true, false, "", DisplayName = "All ranges consistent")] + [DataRow(false, true, false, false, false, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False.", DisplayName = "Inconsistent MinInclusive")] + [DataRow(false, true, false, true, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent MaxInclusive")] + [DataRow(false, true, false, false, true, true, false, "Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. IsMinInclusive found: True, False, IsMaxInclusive found: False, True.", DisplayName = "Inconsistent Min and Max Inclusive")] + [DataRow(true, null, null, null, null, null, null, "", DisplayName = "Empty range list")] + public void GivenListOfFeedRangesEnsureConsistentInclusivityValidatesRangesTest( + bool shouldNotThrow, + bool? isMin1, + bool? isMax1, + bool? isMin2, + bool? isMax2, + bool? isMin3, + bool? isMax3, + string expectedMessage) + { + List> ranges = new List>(); + + if (isMin1.HasValue && isMax1.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "A", max: "B", isMinInclusive: isMin1.Value, isMaxInclusive: isMax1.Value)); + } + + if (isMin2.HasValue && isMax2.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "C", max: "D", isMinInclusive: isMin2.Value, isMaxInclusive: isMax2.Value)); + } + + if (isMin3.HasValue && isMax3.HasValue) + { + ranges.Add(new Documents.Routing.Range(min: "E", max: "F", isMinInclusive: isMin3.Value, isMaxInclusive: isMax3.Value)); + } + + InvalidOperationException exception = default; + + if (!shouldNotThrow) + { + exception = Assert.ThrowsException(() => ContainerCore.EnsureConsistentInclusivity(ranges)); + + Assert.IsNotNull(exception); + Assert.AreEqual(expected: expectedMessage, actual: exception.Message); + + return; + } + + Assert.IsNull(exception); + } + } +} From c43d0636a728171df95a9ca58ebaec45885ec59c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 12:25:59 -0400 Subject: [PATCH 082/145] removed duplicated test data row. --- .../IsFeedRangePartOfAsyncTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index c85bb9deae..bc92afb540 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -691,7 +691,7 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// The expected actualIsSubset: true if the child is a subset, false otherwise. [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] @@ -700,7 +700,6 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ has min equal to parent but max out of range")] [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Parent A-Z encapsulates child B-Y")] public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) { Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); From b6e5e2980d3d82f3a74f7e3d70a48a541d2b12af Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 12:35:12 -0400 Subject: [PATCH 083/145] removal of unnecessary comments since I am describing the scenarios. --- .../IsFeedRangePartOfAsyncTests.cs | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index bc92afb540..20955e7277 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -437,16 +437,16 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // child is same as the parent, which makes it a subset + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; } /// @@ -462,13 +462,13 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxI /// private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; } /// @@ -531,16 +531,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; } /// @@ -556,13 +556,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc /// private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; } /// @@ -578,16 +578,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // child is same as the parent, which makes it a subset - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // child is subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; } /// @@ -603,13 +603,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndP /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is not a subset of parent - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // child is overlap, but not a subset of the parent + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; } /// From f860d126cb463f36d9e576538c92c9eaef0478c6 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 12:38:04 -0400 Subject: [PATCH 084/145] missed a container instance --- .../IsFeedRangePartOfAsyncTests.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 20955e7277..f4f7fd9f67 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -392,17 +392,9 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi bool parentIsMaxInclusive, bool expectedIsFeedRangePartOfAsync) { - Container container = default; - try { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - - bool actualIsFeedRangePartOfAsync = await ((ContainerInternal)container).IsFeedRangePartOfAsync( + bool actualIsFeedRangePartOfAsync = await this.containerInternal.IsFeedRangePartOfAsync( parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), cancellationToken: CancellationToken.None); From 801c1a808529c4d8501f7d4b1e7f6281e808ccfa Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 12:39:26 -0400 Subject: [PATCH 085/145] missed another container reference --- .../IsFeedRangePartOfAsyncTests.cs | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index f4f7fd9f67..66fdf19286 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -321,18 +321,10 @@ await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfA private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) where TException : Exception { - Container container = default; - try { - ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); - - container = containerResponse.Container; - TException exception = await Assert.ThrowsExceptionAsync( - async () => await ((ContainerInternal)container).IsFeedRangePartOfAsync( + async () => await this.containerInternal.IsFeedRangePartOfAsync( parentFeedRange: feedRange, childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); @@ -344,13 +336,6 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang { Assert.Fail(exception.Message); } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } } /// @@ -405,13 +390,6 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi { Assert.Fail(exception.Message); } - finally - { - if (container != null) - { - await container.DeleteContainerAsync(); - } - } } /// From 4c84f8e37e7ab54a6a0c6a2a2c4e1e790c87278f Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 12:56:28 -0400 Subject: [PATCH 086/145] revert since new tests have new tests file --- .../CosmosContainerTests.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index e11c320aa2..f3befc588b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -6,27 +6,29 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; using System.Collections.Generic; - using System.Collections.ObjectModel; + using System.Collections.ObjectModel; using System.IO; using System.Linq; - using System.Net; + using System.Net; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.VisualStudio.TestTools.UnitTesting; + using HttpConstants = Microsoft.Azure.Documents.HttpConstants; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - + using Newtonsoft.Json.Linq; + [TestClass] public class CosmosContainerTests { private CosmosClient cosmosClient = null; - private Cosmos.Database cosmosDatabase = null; + private Cosmos.Database cosmosDatabase = null; private static long ToEpoch(DateTime dateTime) { return (long)(dateTime - new DateTime(1970, 1, 1)).TotalSeconds; - } - + } + [TestInitialize] public async Task TestInit() { @@ -629,7 +631,7 @@ public async Task CreateContainerIfNotExistsAsyncTest() requestChargeHandler.TotalRequestCharges = 0; ContainerResponse createWithConflictResponse = await database.CreateContainerIfNotExistsAsync( - Guid.NewGuid().ToString(), + Guid.NewGuid().ToString(), "/pk"); Assert.AreEqual(requestChargeHandler.TotalRequestCharges, createWithConflictResponse.RequestCharge); @@ -749,9 +751,9 @@ public async Task CreateContainerIfNotExistsAsyncForMultiHashCollectionsTest() Assert.AreEqual(nameof(settings.PartitionKey), ex.ParamName); Assert.IsTrue(ex.Message.Contains(string.Format( ClientResources.PartitionKeyPathConflict, - string.Join(",", partitionKeyPath2), + string.Join(",",partitionKeyPath2), containerName, - string.Join(",", partitionKeyPath1)))); + string.Join(",",partitionKeyPath1)))); } // Mismatch in the 2nd path @@ -772,10 +774,10 @@ public async Task CreateContainerIfNotExistsAsyncForMultiHashCollectionsTest() string.Join(",", partitionKeyPath3), containerName, string.Join(",", partitionKeyPath1)))); - } - - - //Create and fetch container with same paths + } + + + //Create and fetch container with same paths List partitionKeyPath4 = new List(); partitionKeyPath4.Add("/users"); partitionKeyPath4.Add("/sessionId"); @@ -1394,7 +1396,7 @@ public async Task ClientEncryptionPolicyTest() { Id = containerName, PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash }, - ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths: paths, policyFormatVersion: 2) + ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths:paths,policyFormatVersion:2) }; ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(setting); @@ -1423,9 +1425,9 @@ public async Task ClientEncryptionPolicyTest() ContainerResponse readResponse = await container.ReadContainerAsync(); Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); - Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); - - // version 1 test. + Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); + + // version 1 test. containerName = Guid.NewGuid().ToString(); partitionKeyPath = "/users"; paths = new Collection() @@ -1473,9 +1475,9 @@ public async Task ClientEncryptionPolicyTest() readResponse = await container.ReadContainerAsync(); Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); - Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); - - // replace without updating CEP should be successful + Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy); + + // replace without updating CEP should be successful readResponse.Resource.IndexingPolicy = new Cosmos.IndexingPolicy() { IndexingMode = Cosmos.IndexingMode.None, @@ -1528,7 +1530,7 @@ public async Task ClientEncryptionPolicyFailureTest() }; Assert.Fail("Creating ContainerProperties should have failed."); - } + } catch (ArgumentException ex) { Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'."), ex.Message); @@ -1561,7 +1563,7 @@ public async Task ClientEncryptionPolicyFailureTest() { Id = containerName, PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash }, - ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsList) + ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsList) }; Assert.Fail("Creating ContainerProperties should have failed."); @@ -1781,6 +1783,6 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsFalse(containerCore.LinkUri.ToString().StartsWith("/")); Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString()); - } + } } } From 846dfb4ac6fec808e4177ac6bb595edb5be48392 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 14:53:37 -0400 Subject: [PATCH 087/145] add another test scenario --- .../IsFeedRangePartOfAsyncTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 66fdf19286..9f69b0119f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -662,6 +662,7 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] + [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] From 91708f92f69cbc2adeda6992c4f298ca8e79f9d8 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 14:54:16 -0400 Subject: [PATCH 088/145] fix displayname --- .../IsFeedRangePartOfAsyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 9f69b0119f..f2b25ffacb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -662,7 +662,7 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] - [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] + [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Z is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] From 769c8e4478fbd0d6da3cf96bf0bd9c089656e159 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 14:59:55 -0400 Subject: [PATCH 089/145] more scenarios --- .../IsFeedRangePartOfAsyncTests.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index f2b25ffacb..2ad1449fd0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -661,14 +661,15 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// The expected actualIsSubset: true if the child is a subset, false otherwise. [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] - [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Z is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] - [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z equals parent A-Z")] - [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y has min out of parent A-Z")] - [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ has max out of parent A-Z")] - [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ is completely outside parent A-Z")] - [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z has max equal to parent but min out of range")] - [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ has min equal to parent but max out of range")] + [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y (IsMinInclusive = false, IsMaxInclusive = false) is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] + [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Z (IsMinInclusive = false, IsMaxInclusive = true) is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] + [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z (IsMinInclusive = true, IsMaxInclusive = true) equals parent A-Z")] + [DataRow("A", "Z", "A", "Y", true, DisplayName = "Child A-Y (IsMinInclusive = true, IsMaxInclusive = false) equals parent A-Z")] + [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y (IsMinInclusive = false, IsMaxInclusive = false) has min out of parent A-Z")] + [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ (IsMinInclusive = false, IsMaxInclusive = false) has max out of parent A-Z")] + [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ (IsMinInclusive = false, IsMaxInclusive = false) is completely outside parent A-Z")] + [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z (IsMinInclusive = false, IsMaxInclusive = false) has max equal to parent but min out of range")] + [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ (IsMinInclusive = false, IsMaxInclusive = false) has min equal to parent but max out of range")] [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) From e0319dd7526d8930c4f37773b897990f5d747fa9 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 16 Sep 2024 16:14:59 -0400 Subject: [PATCH 090/145] move ensureConsistentInclusivity to check on effectiveRanges outside of merge. --- .../Resource/Container/ContainerCore.Items.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 5a2612ae5d..8e73ead7d9 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1298,19 +1298,24 @@ public override async Task IsFeedRangePartOfAsync( IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); + List> parentEffectiveRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); + + List> childEffectiveRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( + routingMapProvider: routingMapProvider, + containerRid: containerRId, + partitionKeyDefinition: partitionKeyDefinition, + trace: trace); + + ContainerCore.EnsureConsistentInclusivity(parentEffectiveRanges); + ContainerCore.EnsureConsistentInclusivity(childEffectiveRanges); + return ContainerCore.IsSubset( - parentRange: ContainerCore.MergeRanges( - ranges: await parentFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace)), - childRange: ContainerCore.MergeRanges( - ranges: await childFeedRangeInternal.GetEffectiveRangesAsync( - routingMapProvider: routingMapProvider, - containerRid: containerRId, - partitionKeyDefinition: partitionKeyDefinition, - trace: trace))); + parentRange: ContainerCore.MergeRanges(parentEffectiveRanges), + childRange: ContainerCore.MergeRanges(childEffectiveRanges)); } catch (DocumentClientException dce) { @@ -1336,8 +1341,6 @@ private static Documents.Routing.Range MergeRanges( return ranges.First(); } - ContainerCore.EnsureConsistentInclusivity(ranges); - ranges.Sort(Documents.Routing.Range.MinComparer.Instance); Documents.Routing.Range firstRange = ranges.First(); From e7bde8831a32fd3ffa0befbc5fb69227d0907ace Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 17 Sep 2024 10:20:51 -0400 Subject: [PATCH 091/145] adding comments, but thinking of a better way to deal and manage this amount of data and scenaros where it easier to understand. will create another PR for improvements. --- .../IsFeedRangePartOfAsyncTests.cs | 138 +++++++++--------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 2ad1449fd0..1f133a850f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -407,16 +408,17 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + } /// @@ -432,13 +434,13 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxI /// private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3333333333333333, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range from 3333333333333333 to just before 6666666666666666 is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range from 7333333333333333 to just before FFFFFFFFFFFFFFFF is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 7333333333333333, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. } /// @@ -454,16 +456,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndPa /// private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. } /// @@ -479,13 +481,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc /// private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends just before BFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. } /// @@ -501,16 +503,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// @@ -526,13 +528,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc /// private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3333333333333333, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to just before 6666666666666666, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to just before FFFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 7333333333333333, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// @@ -548,16 +550,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// @@ -573,13 +575,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndP /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// From 4b3b079a597f2c933539929f6938ad17c09dc68c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 17 Sep 2024 10:55:32 -0400 Subject: [PATCH 092/145] introducing Gherkin interpreter to have a discussion with Team to see if this is a viable solution. --- .../IsFeedRangePartOfAsyncTests.cs | 172 ++++++++++++++++-- .../Utils/IsFeedRangePartOfTestHelper.cs | 145 +++++++++++++++ 2 files changed, 299 insertions(+), 18 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 1f133a850f..207e4c3d3a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -7,11 +7,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.IsFeedRangePartOfTestHelper; [TestClass] public class IsFeedRangePartOfAsyncTests @@ -20,6 +20,7 @@ public class IsFeedRangePartOfAsyncTests private Cosmos.Database cosmosDatabase = null; private ContainerInternal containerInternal = null; private ContainerInternal hierarchicalContainerInternal = null; + private static readonly GherkinInterpreter interpreter = new GherkinInterpreter(); [TestInitialize] public async Task TestInit() @@ -550,16 +551,96 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3FFFFFFFFFFFFFFF + And the parent range starts from a lower bound minimum + And the parent range ending at FFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 3FFFFFFFFFFFFFFF + And the child range ending at 7FFFFFFFFFFFFFFF + And the parent range starts from a lower bound minimum + And the parent range ending at FFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 7FFFFFFFFFFFFFFF + And the child range ending at BFFFFFFFFFFFFFFF + And the parent range starts from a lower bound minimum + And the parent range ending at FFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from BFFFFFFFFFFFFFFF + And the child range ending at FFFFFFFFFFFFFFFF + And the parent range starts from a lower bound minimum + And the parent range ending at FFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3FFFFFFFFFFFFFFF + And the parent range starts from a lower bound minimum + And the parent range ending at 3FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 3FFFFFFFFFFFFFFF + And the child range ending at 4CCCCCCCCCCCCCCC + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 4CCCCCCCCCCCCCCC + And the child range ending at 5999999999999999 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 5999999999999999 + And the child range ending at 6666666666666666 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 6666666666666666 + And the child range ending at 7333333333333333 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 7333333333333333 + And the child range ending at 7FFFFFFFFFFFFFFF + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range fits entirely within the parent range + "); + } /// @@ -575,13 +656,68 @@ private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndP /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3FFFFFFFFFFFFFFF + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3FFFFFFFFFFFFFFF + And the parent range starts from 7FFFFFFFFFFFFFFF + And the parent range ending at BFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3FFFFFFFFFFFFFFF + And the parent range starts from BFFFFFFFFFFFFFFF + And the parent range ending at FFFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 3333333333333333 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 3333333333333333 + And the child range ending at 6666666666666666 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from 7333333333333333 + And the child range ending at FFFFFFFFFFFFFFFF + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); + + yield return interpreter.ParseGherkinToObjectArray(@" + Given the child range starts from a lower bound minimum + And the child range ending at 7333333333333333 + And the parent range starts from 3FFFFFFFFFFFFFFF + And the parent range ending at 7FFFFFFFFFFFFFFF + When comparing the child range with the parent range + Then the child range does not fit within the parent range + "); } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs new file mode 100644 index 0000000000..d832c45232 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.Text.RegularExpressions; + + public class IsFeedRangePartOfTestHelper + { + // Context to hold the parsed values + public class GherkinContext + { + public string ChildMin { get; set; } = ""; + + public string ChildMax { get; set; } = ""; + + public bool ChildMaxInclusive { get; set; } = false; + + public string ParentMin { get; set; } = ""; + + public string ParentMax { get; set; } = ""; + + public bool ParentMaxInclusive { get; set; } = false; + + public bool DoesFit { get; set; } = false; + } + + // The Expression interface + public interface IGherkinExpression + { + void Interpret(GherkinContext context, string line); + } + + // Concrete Expressions + public class ChildRangeStartExpression : IGherkinExpression + { + public void Interpret(GherkinContext context, string line) + { + Match match = Regex.Match(line, @"Given the child range starts from (.+)"); + if (match.Success) + { + context.ChildMin = match.Groups[1].Value.Trim() == "a lower bound minimum" ? "" : match.Groups[1].Value.Trim(); + } + } + } + + public class ChildRangeEndExpression : IGherkinExpression + { + public void Interpret(GherkinContext context, string line) + { + Match match = Regex.Match(line, @"And the child range (ending just before|ending at) (.+)"); + if (match.Success) + { + context.ChildMaxInclusive = match.Groups[1].Value == "ending at"; + context.ChildMax = match.Groups[2].Value.Trim(); + } + } + } + + public class ParentRangeStartExpression : IGherkinExpression + { + public void Interpret(GherkinContext context, string line) + { + Match match = Regex.Match(line, @"And the parent range starts from (.+)"); + if (match.Success) + { + context.ParentMin = match.Groups[1].Value.Trim() == "a lower bound minimum" ? "" : match.Groups[1].Value.Trim(); + } + } + } + + public class ParentRangeEndExpression : IGherkinExpression + { + public void Interpret(GherkinContext context, string line) + { + Match match = Regex.Match(line, @"And the parent range (ending just before|ending at) (.+)"); + if (match.Success) + { + context.ParentMaxInclusive = match.Groups[1].Value == "ending at"; + context.ParentMax = match.Groups[2].Value.Trim(); + } + } + } + + public class ComparisonExpression : IGherkinExpression + { + public void Interpret(GherkinContext context, string line) + { + Match match = Regex.Match(line, @"Then the child range (fits entirely within|does not fit within) the parent range"); + if (match.Success) + { + context.DoesFit = match.Groups[1].Value == "fits entirely within"; + } + } + } + + // The GherkinInterpreter that uses all expressions + public class GherkinInterpreter + { + private readonly List _expressions; + + public GherkinInterpreter() + { + // Register all the concrete expressions + this._expressions = new List + { + new ChildRangeStartExpression(), + new ChildRangeEndExpression(), + new ParentRangeStartExpression(), + new ParentRangeEndExpression(), + new ComparisonExpression() + }; + } + + public object[] ParseGherkinToObjectArray(string gherkin) + { + GherkinContext context = new GherkinContext(); + string[] lines = gherkin.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string line in lines) + { + foreach (IGherkinExpression expression in this._expressions) + { + expression.Interpret(context, line.Trim()); + } + } + + // Return the object array after interpreting the entire Gherkin + return new object[] + { + context.ChildMin, + context.ChildMax, + context.ChildMaxInclusive, + context.ParentMin, + context.ParentMax, + context.ParentMaxInclusive, + context.DoesFit + }; + } + } + } +} From bfa9740f80106510e8fa2f3b4f822df2734561ad Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 17 Sep 2024 11:46:40 -0400 Subject: [PATCH 093/145] remove gherkin. will follow up on dynamic building of scenarios. --- .../IsFeedRangePartOfAsyncTests.cs | 171 ++---------------- .../Utils/IsFeedRangePartOfTestHelper.cs | 145 --------------- 2 files changed, 17 insertions(+), 299 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 207e4c3d3a..a987a4b953 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -11,7 +11,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.IsFeedRangePartOfTestHelper; [TestClass] public class IsFeedRangePartOfAsyncTests @@ -20,7 +19,6 @@ public class IsFeedRangePartOfAsyncTests private Cosmos.Database cosmosDatabase = null; private ContainerInternal containerInternal = null; private ContainerInternal hierarchicalContainerInternal = null; - private static readonly GherkinInterpreter interpreter = new GherkinInterpreter(); [TestInitialize] public async Task TestInit() @@ -551,96 +549,16 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3FFFFFFFFFFFFFFF - And the parent range starts from a lower bound minimum - And the parent range ending at FFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 3FFFFFFFFFFFFFFF - And the child range ending at 7FFFFFFFFFFFFFFF - And the parent range starts from a lower bound minimum - And the parent range ending at FFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 7FFFFFFFFFFFFFFF - And the child range ending at BFFFFFFFFFFFFFFF - And the parent range starts from a lower bound minimum - And the parent range ending at FFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from BFFFFFFFFFFFFFFF - And the child range ending at FFFFFFFFFFFFFFFF - And the parent range starts from a lower bound minimum - And the parent range ending at FFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3FFFFFFFFFFFFFFF - And the parent range starts from a lower bound minimum - And the parent range ending at 3FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 3FFFFFFFFFFFFFFF - And the child range ending at 4CCCCCCCCCCCCCCC - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 4CCCCCCCCCCCCCCC - And the child range ending at 5999999999999999 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 5999999999999999 - And the child range ending at 6666666666666666 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 6666666666666666 - And the child range ending at 7333333333333333 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 7333333333333333 - And the child range ending at 7FFFFFFFFFFFFFFF - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range fits entirely within the parent range - "); - + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// @@ -656,68 +574,13 @@ Then the child range fits entirely within the parent range /// private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() { - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3FFFFFFFFFFFFFFF - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3FFFFFFFFFFFFFFF - And the parent range starts from 7FFFFFFFFFFFFFFF - And the parent range ending at BFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3FFFFFFFFFFFFFFF - And the parent range starts from BFFFFFFFFFFFFFFF - And the parent range ending at FFFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 3333333333333333 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 3333333333333333 - And the child range ending at 6666666666666666 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from 7333333333333333 - And the child range ending at FFFFFFFFFFFFFFFF - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); - - yield return interpreter.ParseGherkinToObjectArray(@" - Given the child range starts from a lower bound minimum - And the child range ending at 7333333333333333 - And the parent range starts from 3FFFFFFFFFFFFFFF - And the parent range ending at 7FFFFFFFFFFFFFFF - When comparing the child range with the parent range - Then the child range does not fit within the parent range - "); + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs deleted file mode 100644 index d832c45232..0000000000 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/IsFeedRangePartOfTestHelper.cs +++ /dev/null @@ -1,145 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests -{ - using System; - using System.Collections.Generic; - using System.Text.RegularExpressions; - - public class IsFeedRangePartOfTestHelper - { - // Context to hold the parsed values - public class GherkinContext - { - public string ChildMin { get; set; } = ""; - - public string ChildMax { get; set; } = ""; - - public bool ChildMaxInclusive { get; set; } = false; - - public string ParentMin { get; set; } = ""; - - public string ParentMax { get; set; } = ""; - - public bool ParentMaxInclusive { get; set; } = false; - - public bool DoesFit { get; set; } = false; - } - - // The Expression interface - public interface IGherkinExpression - { - void Interpret(GherkinContext context, string line); - } - - // Concrete Expressions - public class ChildRangeStartExpression : IGherkinExpression - { - public void Interpret(GherkinContext context, string line) - { - Match match = Regex.Match(line, @"Given the child range starts from (.+)"); - if (match.Success) - { - context.ChildMin = match.Groups[1].Value.Trim() == "a lower bound minimum" ? "" : match.Groups[1].Value.Trim(); - } - } - } - - public class ChildRangeEndExpression : IGherkinExpression - { - public void Interpret(GherkinContext context, string line) - { - Match match = Regex.Match(line, @"And the child range (ending just before|ending at) (.+)"); - if (match.Success) - { - context.ChildMaxInclusive = match.Groups[1].Value == "ending at"; - context.ChildMax = match.Groups[2].Value.Trim(); - } - } - } - - public class ParentRangeStartExpression : IGherkinExpression - { - public void Interpret(GherkinContext context, string line) - { - Match match = Regex.Match(line, @"And the parent range starts from (.+)"); - if (match.Success) - { - context.ParentMin = match.Groups[1].Value.Trim() == "a lower bound minimum" ? "" : match.Groups[1].Value.Trim(); - } - } - } - - public class ParentRangeEndExpression : IGherkinExpression - { - public void Interpret(GherkinContext context, string line) - { - Match match = Regex.Match(line, @"And the parent range (ending just before|ending at) (.+)"); - if (match.Success) - { - context.ParentMaxInclusive = match.Groups[1].Value == "ending at"; - context.ParentMax = match.Groups[2].Value.Trim(); - } - } - } - - public class ComparisonExpression : IGherkinExpression - { - public void Interpret(GherkinContext context, string line) - { - Match match = Regex.Match(line, @"Then the child range (fits entirely within|does not fit within) the parent range"); - if (match.Success) - { - context.DoesFit = match.Groups[1].Value == "fits entirely within"; - } - } - } - - // The GherkinInterpreter that uses all expressions - public class GherkinInterpreter - { - private readonly List _expressions; - - public GherkinInterpreter() - { - // Register all the concrete expressions - this._expressions = new List - { - new ChildRangeStartExpression(), - new ChildRangeEndExpression(), - new ParentRangeStartExpression(), - new ParentRangeEndExpression(), - new ComparisonExpression() - }; - } - - public object[] ParseGherkinToObjectArray(string gherkin) - { - GherkinContext context = new GherkinContext(); - string[] lines = gherkin.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (string line in lines) - { - foreach (IGherkinExpression expression in this._expressions) - { - expression.Interpret(context, line.Trim()); - } - } - - // Return the object array after interpreting the entire Gherkin - return new object[] - { - context.ChildMin, - context.ChildMax, - context.ChildMaxInclusive, - context.ParentMin, - context.ParentMax, - context.ParentMaxInclusive, - context.DoesFit - }; - } - } - } -} From b82a731c9de69d59c759033dc5c4eb40f2d76207 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 09:23:27 -0400 Subject: [PATCH 094/145] reverting to master --- .../Resource/Container/ContainerInlineCore.cs | 1354 ++++++++--------- 1 file changed, 677 insertions(+), 677 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 984166faa3..2f9184527e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -1,678 +1,678 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed; - using Microsoft.Azure.Cosmos.Query.Core.Monads; - using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.ReadFeed; - using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; - using Microsoft.Azure.Cosmos.Tracing; - - // This class acts as a wrapper for environments that use SynchronizationContext. - internal sealed class ContainerInlineCore : ContainerCore - { - internal ContainerInlineCore( - CosmosClientContext clientContext, - DatabaseInternal database, - string containerId, - CosmosQueryClient cosmosQueryClient = null) - : base(clientContext, - database, - containerId, - cosmosQueryClient) - { - } - - public override Task ReadContainerAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadContainerAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadContainerAsync(trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReadContainerStreamAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadContainerStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadContainerStreamAsync(trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReplaceContainerAsync( - ContainerProperties containerProperties, - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceContainerAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceContainerAsync(containerProperties, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReplaceContainerStreamAsync( - ContainerProperties containerProperties, - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceContainerStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceContainerStreamAsync(containerProperties, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task DeleteContainerAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(DeleteContainerAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Delete, - requestOptions: requestOptions, - task: (trace) => base.DeleteContainerAsync(trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.DeleteContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task DeleteContainerStreamAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(DeleteContainerStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Delete, - requestOptions: requestOptions, - task: (trace) => base.DeleteContainerStreamAsync(trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.DeleteContainer, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReadThroughputAsync(CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadThroughputAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: null, - task: (trace) => base.ReadThroughputAsync(trace, cancellationToken)); - } - - public override Task ReadThroughputAsync( - RequestOptions requestOptions, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadThroughputAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadThroughputAsync(requestOptions, trace, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadThroughput, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReplaceThroughputAsync( - int throughput, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceThroughputAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceThroughputAsync(throughput, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughput, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReplaceThroughputAsync( - ThroughputProperties throughputProperties, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceThroughputAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceThroughputAsync(throughputProperties, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughput, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReadThroughputIfExistsAsync(RequestOptions requestOptions, CancellationToken cancellationToken) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadThroughputIfExistsAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadThroughputIfExistsAsync(requestOptions, trace, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadThroughputIfExists, (response) => new OpenTelemetryResponse(response))); - } - - public override Task ReplaceThroughputIfExistsAsync(ThroughputProperties throughput, RequestOptions requestOptions, CancellationToken cancellationToken) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceThroughputIfExistsAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceThroughputIfExistsAsync(throughput, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughputIfExists, (response) => new OpenTelemetryResponse(response))); - } - - public override Task CreateItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - Task func(ITrace trace) - { - return base.CreateItemStreamAsync( - streamPayload, - partitionKey, - trace, - requestOptions, - cancellationToken); - } - - return this.ClientContext.OperationHelperAsync( - operationName: nameof(CreateItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Create, - requestOptions: requestOptions, - task: func, - openTelemetry: new (OpenTelemetryConstants.Operations.CreateItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> CreateItemAsync(T item, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(CreateItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Create, - requestOptions: requestOptions, - task: (trace) => base.CreateItemAsync(item, trace, partitionKey, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.CreateItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task ReadItemStreamAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadItemStreamAsync(id, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> ReadItemAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: requestOptions, - task: (trace) => base.ReadItemAsync(id, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task UpsertItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(UpsertItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Upsert, - requestOptions: requestOptions, - task: (trace) => base.UpsertItemStreamAsync(streamPayload, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.UpsertItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> UpsertItemAsync( - T item, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(UpsertItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Upsert, - requestOptions: requestOptions, - task: (trace) => base.UpsertItemAsync(item, trace, partitionKey, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.UpsertItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task ReplaceItemStreamAsync( - Stream streamPayload, - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceItemStreamAsync(streamPayload, id, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> ReplaceItemAsync( - T item, - string id, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReplaceItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Replace, - requestOptions: requestOptions, - task: (trace) => base.ReplaceItemAsync(item, id, trace, partitionKey, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task DeleteItemStreamAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(DeleteItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Delete, - requestOptions: requestOptions, - task: (trace) => base.DeleteItemStreamAsync(id, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.DeleteItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> DeleteItemAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(DeleteItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Delete, - requestOptions: requestOptions, - task: (trace) => base.DeleteItemAsync(id, partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.DeleteItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(PatchItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Patch, - requestOptions: requestOptions, - task: (trace) => base.PatchItemStreamAsync(id, partitionKey, patchOperations, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - Stream streamPayload, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(PatchItemStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Patch, - requestOptions: requestOptions, - task: (trace) => base.PatchItemStreamAsync(id, partitionKey, streamPayload, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task> PatchItemAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(PatchItemAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Patch, - requestOptions: requestOptions, - task: (trace) => base.PatchItemAsync(id, partitionKey, patchOperations, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), - resourceType: Documents.ResourceType.Document); - } - - public override Task ReadManyItemsStreamAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadManyItemsStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: null, - task: (trace) => base.ReadManyItemsStreamAsync(items, trace, readManyRequestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadManyItems, (response) => new OpenTelemetryResponse(responseMessage: response))); - } - - public override Task> ReadManyItemsAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(ReadManyItemsAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: null, - task: (trace) => base.ReadManyItemsAsync(items, trace, readManyRequestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.ReadManyItems, (response) => new OpenTelemetryResponse(responseMessage: response))); - } - - public override FeedIterator GetItemQueryStreamIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore(base.GetItemQueryStreamIterator( - queryDefinition, - continuationToken, - requestOptions), - this.ClientContext); - } - - public override FeedIterator GetItemQueryIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore(base.GetItemQueryIterator( - queryDefinition, - continuationToken, - requestOptions), - this.ClientContext); - } - - public override FeedIterator GetItemQueryStreamIterator(string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore(base.GetItemQueryStreamIterator( - queryText, - continuationToken, - requestOptions), - this.ClientContext); - } - - public override FeedIterator GetItemQueryIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore(base.GetItemQueryIterator( - queryText, - continuationToken, - requestOptions), - this.ClientContext); - } - - public override IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution = false, - string continuationToken = null, - QueryRequestOptions requestOptions = null, - CosmosLinqSerializerOptions linqSerializerOptions = null) - { - return base.GetItemLinqQueryable( - allowSynchronousQueryExecution, - continuationToken, - requestOptions, - linqSerializerOptions); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate) - { - return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedHandler onChangesDelegate) - { - return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) - { - return base.GetChangeFeedProcessorBuilderWithManualCheckpoint(processorName, onChangesDelegate); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedStreamHandler onChangesDelegate) - { - return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) - { - return base.GetChangeFeedProcessorBuilderWithManualCheckpoint(processorName, onChangesDelegate); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, - ChangesEstimationHandler estimationDelegate, - TimeSpan? estimationPeriod = null) - { - return base.GetChangeFeedEstimatorBuilder(processorName, estimationDelegate, estimationPeriod); - } - - public override ChangeFeedEstimator GetChangeFeedEstimator( - string processorName, - Container leaseContainer) - { - return base.GetChangeFeedEstimator(processorName, leaseContainer); - } - - public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) - { - return base.CreateTransactionalBatch(partitionKey); - } - - public override Task> GetFeedRangesAsync(CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(GetFeedRangesAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.ReadFeed, - requestOptions: null, - task: (trace) => base.GetFeedRangesAsync(trace, cancellationToken)); - } - - public override FeedIterator GetChangeFeedStreamIterator( - ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return base.GetChangeFeedStreamIterator(changeFeedStartFrom, changeFeedMode, changeFeedRequestOptions); - } - - public override FeedIterator GetChangeFeedIterator( - ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = null) - { - return new FeedIteratorInlineCore(base.GetChangeFeedIterator(changeFeedStartFrom, - changeFeedMode, - changeFeedRequestOptions), - this.ClientContext); - } - - public override Task> GetPartitionKeyRangesAsync( - FeedRange feedRange, - CancellationToken cancellationToken = default) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(GetPartitionKeyRangesAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Read, - requestOptions: null, - task: (trace) => base.GetPartitionKeyRangesAsync(feedRange, trace, cancellationToken)); - } - - public override FeedIterator GetItemQueryStreamIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore( - base.GetItemQueryStreamIterator(feedRange, queryDefinition, continuationToken, requestOptions), - this.ClientContext); - } - - public override FeedIterator GetItemQueryIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return new FeedIteratorInlineCore( - base.GetItemQueryIterator(feedRange, queryDefinition, continuationToken, requestOptions), - this.ClientContext); - } - - public override FeedIteratorInternal GetReadFeedIterator(QueryDefinition queryDefinition, QueryRequestOptions queryRequestOptions, string resourceLink, Documents.ResourceType resourceType, string continuationToken, int pageSize) - { - return base.GetReadFeedIterator(queryDefinition, queryRequestOptions, resourceLink, resourceType, continuationToken, pageSize); - } - - public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( - ChangeFeedCrossFeedRangeState state, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = default) - { - return base.GetChangeFeedAsyncEnumerable(state, changeFeedMode, changeFeedRequestOptions); - } - - public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( - ReadFeedCrossFeedRangeState state, - QueryRequestOptions requestOptions = null) - { - return base.GetReadFeedAsyncEnumerable(state, requestOptions); - } - - public override Task DeleteAllItemsByPartitionKeyStreamAsync( - Cosmos.PartitionKey partitionKey, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - return this.ClientContext.OperationHelperAsync( - operationName: nameof(DeleteAllItemsByPartitionKeyStreamAsync), - containerName: this.Id, - databaseName: this.Database.Id, - operationType: Documents.OperationType.Delete, - requestOptions: requestOptions, - task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), - openTelemetry: new (OpenTelemetryConstants.Operations.DeleteAllItemsByPartitionKey, (response) => new OpenTelemetryResponse(response))); - } - } +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; + using Microsoft.Azure.Cosmos.Query.Core.Monads; + using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.ReadFeed; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; + using Microsoft.Azure.Cosmos.Tracing; + + // This class acts as a wrapper for environments that use SynchronizationContext. + internal sealed class ContainerInlineCore : ContainerCore + { + internal ContainerInlineCore( + CosmosClientContext clientContext, + DatabaseInternal database, + string containerId, + CosmosQueryClient cosmosQueryClient = null) + : base(clientContext, + database, + containerId, + cosmosQueryClient) + { + } + + public override Task ReadContainerAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadContainerAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadContainerAsync(trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReadContainerStreamAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadContainerStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadContainerStreamAsync(trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReplaceContainerAsync( + ContainerProperties containerProperties, + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceContainerAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceContainerAsync(containerProperties, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReplaceContainerStreamAsync( + ContainerProperties containerProperties, + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceContainerStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceContainerStreamAsync(containerProperties, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task DeleteContainerAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(DeleteContainerAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Delete, + requestOptions: requestOptions, + task: (trace) => base.DeleteContainerAsync(trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.DeleteContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task DeleteContainerStreamAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(DeleteContainerStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Delete, + requestOptions: requestOptions, + task: (trace) => base.DeleteContainerStreamAsync(trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.DeleteContainer, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReadThroughputAsync(CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadThroughputAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: null, + task: (trace) => base.ReadThroughputAsync(trace, cancellationToken)); + } + + public override Task ReadThroughputAsync( + RequestOptions requestOptions, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadThroughputAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadThroughputAsync(requestOptions, trace, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadThroughput, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReplaceThroughputAsync( + int throughput, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceThroughputAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceThroughputAsync(throughput, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughput, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReplaceThroughputAsync( + ThroughputProperties throughputProperties, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceThroughputAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceThroughputAsync(throughputProperties, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughput, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReadThroughputIfExistsAsync(RequestOptions requestOptions, CancellationToken cancellationToken) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadThroughputIfExistsAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadThroughputIfExistsAsync(requestOptions, trace, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadThroughputIfExists, (response) => new OpenTelemetryResponse(response))); + } + + public override Task ReplaceThroughputIfExistsAsync(ThroughputProperties throughput, RequestOptions requestOptions, CancellationToken cancellationToken) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceThroughputIfExistsAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceThroughputIfExistsAsync(throughput, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceThroughputIfExists, (response) => new OpenTelemetryResponse(response))); + } + + public override Task CreateItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + Task func(ITrace trace) + { + return base.CreateItemStreamAsync( + streamPayload, + partitionKey, + trace, + requestOptions, + cancellationToken); + } + + return this.ClientContext.OperationHelperAsync( + operationName: nameof(CreateItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Create, + requestOptions: requestOptions, + task: func, + openTelemetry: new (OpenTelemetryConstants.Operations.CreateItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> CreateItemAsync(T item, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(CreateItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Create, + requestOptions: requestOptions, + task: (trace) => base.CreateItemAsync(item, trace, partitionKey, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.CreateItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task ReadItemStreamAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadItemStreamAsync(id, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> ReadItemAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: requestOptions, + task: (trace) => base.ReadItemAsync(id, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task UpsertItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(UpsertItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Upsert, + requestOptions: requestOptions, + task: (trace) => base.UpsertItemStreamAsync(streamPayload, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.UpsertItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> UpsertItemAsync( + T item, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(UpsertItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Upsert, + requestOptions: requestOptions, + task: (trace) => base.UpsertItemAsync(item, trace, partitionKey, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.UpsertItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task ReplaceItemStreamAsync( + Stream streamPayload, + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceItemStreamAsync(streamPayload, id, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> ReplaceItemAsync( + T item, + string id, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReplaceItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Replace, + requestOptions: requestOptions, + task: (trace) => base.ReplaceItemAsync(item, id, trace, partitionKey, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReplaceItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task DeleteItemStreamAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(DeleteItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Delete, + requestOptions: requestOptions, + task: (trace) => base.DeleteItemStreamAsync(id, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.DeleteItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> DeleteItemAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(DeleteItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Delete, + requestOptions: requestOptions, + task: (trace) => base.DeleteItemAsync(id, partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.DeleteItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(PatchItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Patch, + requestOptions: requestOptions, + task: (trace) => base.PatchItemStreamAsync(id, partitionKey, patchOperations, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + Stream streamPayload, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(PatchItemStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Patch, + requestOptions: requestOptions, + task: (trace) => base.PatchItemStreamAsync(id, partitionKey, streamPayload, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task> PatchItemAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(PatchItemAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Patch, + requestOptions: requestOptions, + task: (trace) => base.PatchItemAsync(id, partitionKey, patchOperations, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.PatchItem, (response) => new OpenTelemetryResponse(response)), + resourceType: Documents.ResourceType.Document); + } + + public override Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadManyItemsStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: null, + task: (trace) => base.ReadManyItemsStreamAsync(items, trace, readManyRequestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadManyItems, (response) => new OpenTelemetryResponse(responseMessage: response))); + } + + public override Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(ReadManyItemsAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: null, + task: (trace) => base.ReadManyItemsAsync(items, trace, readManyRequestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.ReadManyItems, (response) => new OpenTelemetryResponse(responseMessage: response))); + } + + public override FeedIterator GetItemQueryStreamIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore(base.GetItemQueryStreamIterator( + queryDefinition, + continuationToken, + requestOptions), + this.ClientContext); + } + + public override FeedIterator GetItemQueryIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore(base.GetItemQueryIterator( + queryDefinition, + continuationToken, + requestOptions), + this.ClientContext); + } + + public override FeedIterator GetItemQueryStreamIterator(string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore(base.GetItemQueryStreamIterator( + queryText, + continuationToken, + requestOptions), + this.ClientContext); + } + + public override FeedIterator GetItemQueryIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore(base.GetItemQueryIterator( + queryText, + continuationToken, + requestOptions), + this.ClientContext); + } + + public override IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution = false, + string continuationToken = null, + QueryRequestOptions requestOptions = null, + CosmosLinqSerializerOptions linqSerializerOptions = null) + { + return base.GetItemLinqQueryable( + allowSynchronousQueryExecution, + continuationToken, + requestOptions, + linqSerializerOptions); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate) + { + return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate) + { + return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) + { + return base.GetChangeFeedProcessorBuilderWithManualCheckpoint(processorName, onChangesDelegate); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate) + { + return base.GetChangeFeedProcessorBuilder(processorName, onChangesDelegate); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) + { + return base.GetChangeFeedProcessorBuilderWithManualCheckpoint(processorName, onChangesDelegate); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, + ChangesEstimationHandler estimationDelegate, + TimeSpan? estimationPeriod = null) + { + return base.GetChangeFeedEstimatorBuilder(processorName, estimationDelegate, estimationPeriod); + } + + public override ChangeFeedEstimator GetChangeFeedEstimator( + string processorName, + Container leaseContainer) + { + return base.GetChangeFeedEstimator(processorName, leaseContainer); + } + + public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) + { + return base.CreateTransactionalBatch(partitionKey); + } + + public override Task> GetFeedRangesAsync(CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(GetFeedRangesAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.ReadFeed, + requestOptions: null, + task: (trace) => base.GetFeedRangesAsync(trace, cancellationToken)); + } + + public override FeedIterator GetChangeFeedStreamIterator( + ChangeFeedStartFrom changeFeedStartFrom, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = null) + { + return base.GetChangeFeedStreamIterator(changeFeedStartFrom, changeFeedMode, changeFeedRequestOptions); + } + + public override FeedIterator GetChangeFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = null) + { + return new FeedIteratorInlineCore(base.GetChangeFeedIterator(changeFeedStartFrom, + changeFeedMode, + changeFeedRequestOptions), + this.ClientContext); + } + + public override Task> GetPartitionKeyRangesAsync( + FeedRange feedRange, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(GetPartitionKeyRangesAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Read, + requestOptions: null, + task: (trace) => base.GetPartitionKeyRangesAsync(feedRange, trace, cancellationToken)); + } + + public override FeedIterator GetItemQueryStreamIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore( + base.GetItemQueryStreamIterator(feedRange, queryDefinition, continuationToken, requestOptions), + this.ClientContext); + } + + public override FeedIterator GetItemQueryIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return new FeedIteratorInlineCore( + base.GetItemQueryIterator(feedRange, queryDefinition, continuationToken, requestOptions), + this.ClientContext); + } + + public override FeedIteratorInternal GetReadFeedIterator(QueryDefinition queryDefinition, QueryRequestOptions queryRequestOptions, string resourceLink, Documents.ResourceType resourceType, string continuationToken, int pageSize) + { + return base.GetReadFeedIterator(queryDefinition, queryRequestOptions, resourceLink, resourceType, continuationToken, pageSize); + } + + public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( + ChangeFeedCrossFeedRangeState state, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = default) + { + return base.GetChangeFeedAsyncEnumerable(state, changeFeedMode, changeFeedRequestOptions); + } + + public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( + ReadFeedCrossFeedRangeState state, + QueryRequestOptions requestOptions = null) + { + return base.GetReadFeedAsyncEnumerable(state, requestOptions); + } + + public override Task DeleteAllItemsByPartitionKeyStreamAsync( + Cosmos.PartitionKey partitionKey, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + return this.ClientContext.OperationHelperAsync( + operationName: nameof(DeleteAllItemsByPartitionKeyStreamAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.Delete, + requestOptions: requestOptions, + task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), + openTelemetry: new (OpenTelemetryConstants.Operations.DeleteAllItemsByPartitionKey, (response) => new OpenTelemetryResponse(response))); + } + } } \ No newline at end of file From a45b64625a463c5844d04cb7b9380aa7b1db4945 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 11:30:51 -0400 Subject: [PATCH 095/145] new tests for IsSubset with strategy for creating inclusive or exclusive data. --- .../IsFeedRangePartOfAsyncTests.cs | 122 +++++++++++++----- 1 file changed, 92 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index a987a4b953..ec2d7cd854 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -646,41 +646,65 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF } /// - /// + /// Scenario: Validate whether the child range is a subset of the parent range + /// based on whether the parent and child ranges are max inclusive or max exclusive. + /// + /// Given a parent range with a specified max inclusivity, + /// When a child range is compared with a specified max inclusivity, + /// Then determine if the child range is a subset of the parent range. /// - /// The starting value of the parent range. - /// The ending value of the parent range. - /// The starting value of the child range. - /// The ending value of the child range. - /// The expected actualIsSubset: true if the child is a subset, false otherwise. + /// Indicates whether the parent range's max value is inclusive (true) or exclusive (false). + /// Indicates whether the child range's max value is inclusive (true) or exclusive (false). + /// The expected result: true if the child range is expected to be a subset of the parent range, false otherwise. [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow("A", "Z", "B", "Y", true, DisplayName = "Child B-Y (IsMinInclusive = false, IsMaxInclusive = false) is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Y")] - [DataRow("A", "Z", "B", "Z", true, DisplayName = "Child B-Z (IsMinInclusive = false, IsMaxInclusive = true) is a perfect subset of parent A-Z. Parent A-Z encapsulates child B-Z")] - [DataRow("A", "Z", "A", "Z", true, DisplayName = "Child A-Z (IsMinInclusive = true, IsMaxInclusive = true) equals parent A-Z")] - [DataRow("A", "Z", "A", "Y", true, DisplayName = "Child A-Y (IsMinInclusive = true, IsMaxInclusive = false) equals parent A-Z")] - [DataRow("A", "Z", "@", "Y", false, DisplayName = "Child @-Y (IsMinInclusive = false, IsMaxInclusive = false) has min out of parent A-Z")] - [DataRow("A", "Z", "B", "[", false, DisplayName = "Child B-[ (IsMinInclusive = false, IsMaxInclusive = false) has max out of parent A-Z")] - [DataRow("A", "Z", "@", "[", false, DisplayName = "Child @-[ (IsMinInclusive = false, IsMaxInclusive = false) is completely outside parent A-Z")] - [DataRow("A", "Z", "@", "Z", false, DisplayName = "Child @-Z (IsMinInclusive = false, IsMaxInclusive = false) has max equal to parent but min out of range")] - [DataRow("A", "Z", "A", "[", false, DisplayName = "Child A-[ (IsMinInclusive = false, IsMaxInclusive = false) has min equal to parent but max out of range")] - [DataRow("A", "Z", "", "", false, DisplayName = "Empty child range")] - [DataRow("", "", "B", "Y", false, DisplayName = "Empty parent range with non-empty child range")] - public void ValidateChildRangeIsSubsetOfParentForVariousCasesTest(string parentMinimum, string parentMaximum, string childMinimum, string childMaximum, bool expectedIsSubset) + [DataRow(true, true, true, DisplayName = "Given parent is max inclusive, when child is max inclusive, then child is a subset of parent")] + [DataRow(true, false, true, DisplayName = "Given parent is max inclusive, when child is max exclusive, then child is a subset of parent")] + [DataRow(false, false, true, DisplayName = "Given parent is max exclusive, when child is max exclusive, then child is a subset of parent")] + [DataRow(false, true, false, DisplayName = "Given parent is max exclusive, when child is max inclusive, then child is not a subset of parent")] + public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( + bool isParentMaxInclusive, + bool isChildMaxInclusive, + bool expectedIsSubsetOfParent) { - Documents.Routing.Range parentRange = new Documents.Routing.Range(parentMinimum, parentMaximum, true, true); - Documents.Routing.Range childRange = new Documents.Routing.Range(childMinimum, childMaximum, true, true); - - bool actualIsSubset = ContainerCore.IsSubset(parentRange, childRange); - - Assert.AreEqual(expected: expectedIsSubset, actual: actualIsSubset); + // Define a shared min and max range for both parent and child + string minRange = "A"; // Example shared min value + string maxRange = "Z"; // Example shared max value + + // Create strategies based on inclusivity for the parent and child ranges + IRangeStrategy parentRangeStrategy = isParentMaxInclusive + ? new InclusiveMaxRangeStrategy() + : new ExclusiveMaxRangeStrategy(); + + IRangeStrategy childRangeStrategy = isChildMaxInclusive + ? new InclusiveMaxRangeStrategy() + : new ExclusiveMaxRangeStrategy(); + + // Create range generator contexts for both parent and child using the same min and max + RangeGeneratorContext parentContext = new RangeGeneratorContext(parentRangeStrategy); + RangeGeneratorContext childContext = new RangeGeneratorContext(childRangeStrategy); + + // Generate ranges for parent and child based on shared min and max range + (string min, string max) parentRange = parentContext.GenerateRange(minRange, maxRange); + (string min, string max) childRange = childContext.GenerateRange(minRange, maxRange); + + // Note, the values of isMaxInclusive and isMinInclusive do not affect the outcome of this test, as IsSubset only depends on the min and max values of the parent and child ranges. + + Assert.AreEqual( + expected: expectedIsSubsetOfParent, + actual: ContainerCore.IsSubset( + parentRange: new Documents.Routing.Range( + min: parentRange.min, + max: parentRange.max, + isMinInclusive: true, + isMaxInclusive: isParentMaxInclusive), + childRange: new Documents.Routing.Range( + min: childRange.min, + max: childRange.max, + isMinInclusive: true, + isMaxInclusive: isChildMaxInclusive))); } /// @@ -765,4 +789,42 @@ public void GivenListOfFeedRangesEnsureConsistentInclusivityValidatesRangesTest( Assert.IsNull(exception); } } + + public interface IRangeStrategy + { + (string min, string max) GenerateRange(string min, string maxValue); + } + + public class InclusiveMaxRangeStrategy : IRangeStrategy + { + public (string min, string max) GenerateRange(string min, string maxValue) + { + return (min, maxValue); // max is inclusive + } + } + + public class ExclusiveMaxRangeStrategy : IRangeStrategy + { + public (string min, string max) GenerateRange(string min, string maxValue) + { + // Subtract 1 lexicographically for exclusive max range + string exclusiveMax = (maxValue.Length > 0) ? (char)(maxValue[0] - 1) + maxValue[1..] : maxValue; + return (min, exclusiveMax); + } + } + + public class RangeGeneratorContext + { + private readonly IRangeStrategy rangeStrategy; + + public RangeGeneratorContext(IRangeStrategy rangeStrategy) + { + this.rangeStrategy = rangeStrategy; + } + + public (string min, string max) GenerateRange(string min, string maxValue) + { + return this.rangeStrategy.GenerateRange(min, maxValue); + } + } } From 4f50db247c34866b74833cdb1dd5f3915a5c8c17 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 11:43:35 -0400 Subject: [PATCH 096/145] removed from EncryptionContainer --- .../src/EncryptionContainer.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 743d2d86d7..b898a77179 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,17 +1027,6 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } - - public override Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, - CancellationToken cancellationToken = default) - { - return this.container.IsFeedRangePartOfAsync( - parentFeedRange, - childFeedRange, - cancellationToken); - } #endif private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, From 5e68c032f3e9ccab0c8ee7bdc1ffa54a9df64311 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 11:47:40 -0400 Subject: [PATCH 097/145] need to fix a test. --- .../IsFeedRangePartOfAsyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index ec2d7cd854..9f7f7e5bfd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -688,7 +688,7 @@ public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( // Generate ranges for parent and child based on shared min and max range (string min, string max) parentRange = parentContext.GenerateRange(minRange, maxRange); - (string min, string max) childRange = childContext.GenerateRange(minRange, maxRange); + (string min, string max) childRange = childContext.GenerateRange(parentRange.min, parentRange.max); // Note, the values of isMaxInclusive and isMinInclusive do not affect the outcome of this test, as IsSubset only depends on the min and max values of the parent and child ranges. From 0735b65bb91032f7a3c2ae0b19a66fb25e658c3b Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 12:00:51 -0400 Subject: [PATCH 098/145] revert test. --- .../IsFeedRangePartOfAsyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 9f7f7e5bfd..ec2d7cd854 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -688,7 +688,7 @@ public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( // Generate ranges for parent and child based on shared min and max range (string min, string max) parentRange = parentContext.GenerateRange(minRange, maxRange); - (string min, string max) childRange = childContext.GenerateRange(parentRange.min, parentRange.max); + (string min, string max) childRange = childContext.GenerateRange(minRange, maxRange); // Note, the values of isMaxInclusive and isMinInclusive do not affect the outcome of this test, as IsSubset only depends on the min and max values of the parent and child ranges. From 378353035f4e3e316715a1c1803c109348b7780b Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 12:16:56 -0400 Subject: [PATCH 099/145] adding this back --- .../src/EncryptionContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index b898a77179..49fa42ac37 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,6 +1027,14 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } + + public override Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } #endif private async Task ReadManyItemsHelperAsync( IReadOnlyList<(string id, PartitionKey partitionKey)> items, From 3a2608cbb1c1746eb8daa723aa7835584703a055 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 12:39:10 -0400 Subject: [PATCH 100/145] encryption container fix --- .../src/EncryptionContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 49fa42ac37..5876a200e8 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1027,7 +1027,9 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV processorName, onChangesDelegate); } +#endif +#if SDKPROJECTREF public override Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, From b553c72711eb0df565c231effac253466a7aa642 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 12:58:01 -0400 Subject: [PATCH 101/145] add more comments --- .../Resource/Container/ContainerCore.Items.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8e73ead7d9..8e94d46dc4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1398,28 +1398,36 @@ internal static void EnsureConsistentInclusivity(List> ranges) } } - /// - /// Determines whether the child range is a subset of the parent range. - /// - /// The parent range to check against. - /// The child range that is being evaluated. - /// - /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); - /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// // isSubset will be true because the child range (B-Y) is a subset of the parent range (A-Z). - /// ]]> - /// - /// True if the child range is a subset of the parent range; otherwise, false. + /// + /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. + /// + /// The parent range to check against, representing the outer bounds. + /// The child range being evaluated for subset inclusion within the parent range. + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// ]]> + /// + /// + /// Returns true if the child range is a subset of the parent range, meaning the child range's + /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// internal static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { + // Check if the child's minimum value is within the parent range bool isMinWithinParent = parentRange.Contains(childRange.Min); - bool isMaxWithinParent = parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); + // Check if the child's maximum value is within the parent range + bool isMaxWithinParent = parentRange.Contains(childRange.Max); + + // The child range is a subset if both min and max values are within the parent range return isMinWithinParent && isMaxWithinParent; } } From f2ea6c45c03eca135f09663d214200c2098f7bb6 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 13:06:03 -0400 Subject: [PATCH 102/145] fix broken test --- .../Resource/Container/ContainerCore.Items.cs | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8e94d46dc4..b0609925ca 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1353,29 +1353,29 @@ private static Documents.Routing.Range MergeRanges( isMaxInclusive: firstRange.IsMaxInclusive); } - /// - /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive boundaries. - /// Throws an InvalidOperationException if there are inconsistencies. - /// - /// The list of ranges to validate. - /// - /// Thrown when 'IsMinInclusive' or 'IsMaxInclusive' values are inconsistent across ranges. - /// - /// - /// > ranges = new List> - /// { - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } - /// }; - /// - /// EnsureConsistentInclusivity(ranges); - /// ]]> + /// + /// Validates whether all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive boundaries. + /// Throws an InvalidOperationException if any inconsistencies are found across the ranges. + /// + /// The list of ranges to validate. + /// + /// Thrown when 'IsMinInclusive' or 'IsMaxInclusive' values are inconsistent across ranges. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } + /// }; + /// + /// EnsureConsistentInclusivity(ranges); + /// ]]> /// - internal static void EnsureConsistentInclusivity(List> ranges) - { + internal static void EnsureConsistentInclusivity(List> ranges) +{ bool areAnyDifferent = false; if (ranges.FirstOrDefault() is var firstRange) @@ -1393,7 +1393,7 @@ internal static void EnsureConsistentInclusivity(List> ranges) if (areAnyDifferent) { string result = $"IsMinInclusive found: {string.Join(", ", ranges.Select(range => range.IsMinInclusive).Distinct())}, IsMaxInclusive found: {string.Join(", ", ranges.Select(range => range.IsMaxInclusive).Distinct())}."; - + throw new InvalidOperationException($"Not all 'IsMinInclusive' or 'IsMaxInclusive' values are the same. {result}"); } } @@ -1421,13 +1421,9 @@ internal static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { - // Check if the child's minimum value is within the parent range bool isMinWithinParent = parentRange.Contains(childRange.Min); + bool isMaxWithinParent = parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); - // Check if the child's maximum value is within the parent range - bool isMaxWithinParent = parentRange.Contains(childRange.Max); - - // The child range is a subset if both min and max values are within the parent range return isMinWithinParent && isMaxWithinParent; } } From 43ffba12cf943c51fc6a9e6165d8404d1bea2051 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 13:25:26 -0400 Subject: [PATCH 103/145] add impl --- .../src/EncryptionContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 5876a200e8..d5510c2097 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1035,7 +1035,10 @@ public override Task IsFeedRangePartOfAsync( Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + return this.container.IsFeedRangePartOfAsync( + parentFeedRange, + childFeedRange, + cancellationToken); } #endif private async Task ReadManyItemsHelperAsync( From 2a3a197d7bdadae7bed241b5dbb04e0bbf04ab2d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 13:51:55 -0400 Subject: [PATCH 104/145] reverting --- .../src/Resource/Container/ContainerCore.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 52ab3eae3c..9c2924e227 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -269,14 +269,14 @@ public async Task> GetFeedRangesAsync( forceRefresh: false, trace, cancellationToken); - + try { IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( - containerRId, - ContainerCore.allRanges, - trace, - forceRefresh: true); + containerRId, + ContainerCore.allRanges, + trace, + forceRefresh: true); if (partitionKeyRanges == null) { From feaeb8f5a38299b6203217644bc23779540be286 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 14:25:32 -0400 Subject: [PATCH 105/145] virutal on Container. some things may fail. --- .../src/Resource/Container/Container.cs | 10 ++++++---- .../src/Resource/Container/ContainerInternal.cs | 5 ----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index a3b351691f..e1bd3b3263 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1755,7 +1755,7 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); - +#endif /// /// Determines whether the given child feed range is a subset of the specified parent feed range. /// @@ -1781,10 +1781,12 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the child feed range is a subset of the parent feed range; otherwise, false. - public abstract Task IsFeedRangePartOfAsync( + public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, - CancellationToken cancellationToken = default); -#endif + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } } \ 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 acb85f95a3..cedef3f3de 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -151,11 +151,6 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); - - public abstract Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, - CancellationToken cancellationToken = default); #endif public abstract class TryExecuteQueryResult From 6d77041f81ccdc9a0cb38f0f009b3e1f31cc36f3 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 15:45:15 -0400 Subject: [PATCH 106/145] change test for IsSubset again. Not dynamic anymore. --- .../IsFeedRangePartOfAsyncTests.cs | 142 +++++++----------- 1 file changed, 53 insertions(+), 89 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index ec2d7cd854..bd7b38202a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -645,66 +645,68 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF } } + // GivenParentRangeWhenChildRangeComparedThenValidateIfSubset + /// - /// Feature: Child Range Subset Validation + /// and max value + /// And the parent feed range is inclusive of its min value + /// And the parent feed range is inclusive of its max value + /// When a child feed range with min value and max value is compared + /// And the child feed range is inclusive of its min value + /// And the child feed range is inclusive of its max value + /// Then the result should be indicating if the child is a subset of the parent /// - /// Given a parent range with a specified max inclusivity, - /// When a child range is compared with a specified max inclusivity, - /// Then determine if the child range is a subset of the parent range. + /// Examples: + /// | parentIsMinInclusive | parentIsMaxInclusive | parentMinValue | parentMaxValue | childIsMinInclusive | childIsMaxInclusive | childMinValue | childMaxValue | expectedIsSubset | + /// | true | true | "A" | "Z" | true | true | "A" | "Z" | true | + /// | true | false | "A" | "Y" | true | false | "A" | "Y" | true | + /// | false | true | "B" | "Z" | false | true | "B" | "Z" | true | + /// | false | false | "B" | "Y" | false | false | "B" | "Y" | true | + /// | true | true | "A" | "Z" | true | false | "A" | "Y" | true | + /// | ... | ... | ... | ... | ... | ... | ... | ... | ... | + /// ]]> /// - /// Indicates whether the parent range's max value is inclusive (true) or exclusive (false). - /// Indicates whether the child range's max value is inclusive (true) or exclusive (false). - /// The expected result: true if the child range is expected to be a subset of the parent range, false otherwise. [TestMethod] - [Owner("philipthomas-MSFT")] - [DataRow(true, true, true, DisplayName = "Given parent is max inclusive, when child is max inclusive, then child is a subset of parent")] - [DataRow(true, false, true, DisplayName = "Given parent is max inclusive, when child is max exclusive, then child is a subset of parent")] - [DataRow(false, false, true, DisplayName = "Given parent is max exclusive, when child is max exclusive, then child is a subset of parent")] - [DataRow(false, true, false, DisplayName = "Given parent is max exclusive, when child is max inclusive, then child is not a subset of parent")] + [Owner("philipthomas")] + [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, child is a subset")] + [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] + [DataRow(true, true, "A", "Z", false, true, "B", "Z", true, DisplayName = "Given parent range is fully inclusive and child range starts exclusively after parent, but both end inclusively, child is a subset")] + [DataRow(true, true, "A", "Z", false, false, "B", "Y", true, DisplayName = "Given parent range is fully inclusive and child range is fully exclusive within the parent, child is a subset")] + [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] + [DataRow(true, false, "A", "Y", false, true, "B", "Z", false, DisplayName = "Given child range exceeds the parent’s exclusive max with an inclusive max, child is not a subset")] + [DataRow(true, false, "A", "Y", false, false, "B", "Y", true, DisplayName = "Given parent range is partially inclusive and child is fully exclusive but within the parent’s bounds, child is a subset")] + [DataRow(false, true, "B", "Z", true, true, "A", "Z", false, DisplayName = "Given parent range excludes its min and starts after the child range, but both end inclusively, child is not a subset")] + [DataRow(false, true, "B", "Z", true, false, "A", "Y", false, DisplayName = "Given parent range excludes its min and child range is less than parent’s exclusive min, child is not a subset")] + [DataRow(false, true, "B", "Z", false, true, "B", "Z", false, DisplayName = "Given both parent and child ranges are exclusive at the start and inclusive at the end with the same values, child is not a subset")] + [DataRow(false, true, "B", "Z", false, false, "B", "Y", false, DisplayName = "Given parent range excludes its min and includes its max, and child is fully exclusive within the parent’s bounds, child is not a subset")] + [DataRow(false, false, "B", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range excludes both min and max, and child range is larger with inclusive bounds, child is not a subset")] + [DataRow(false, false, "B", "Y", true, false, "A", "Y", false, DisplayName = "Given parent range excludes both min and max, and child range is larger with an inclusive min, child is not a subset")] + [DataRow(false, false, "B", "Y", false, true, "B", "Z", false, DisplayName = "Given parent range excludes both bounds and child range exceeds parent’s max, child is not a subset")] + [DataRow(false, false, "B", "Y", false, false, "B", "Y", false, DisplayName = "Given both parent and child ranges are fully exclusive and equal, child is not a subset")] + public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( - bool isParentMaxInclusive, - bool isChildMaxInclusive, - bool expectedIsSubsetOfParent) + bool parentIsMinInclusive, + bool parentIsMaxInclusive, + string parentMinValue, + string parentMaxValue, + bool childIsMinInclusive, + bool childIsMaxInclusive, + string childMinValue, + string childMaxValue, + bool expectedIsSubset) { - // Define a shared min and max range for both parent and child - string minRange = "A"; // Example shared min value - string maxRange = "Z"; // Example shared max value - - // Create strategies based on inclusivity for the parent and child ranges - IRangeStrategy parentRangeStrategy = isParentMaxInclusive - ? new InclusiveMaxRangeStrategy() - : new ExclusiveMaxRangeStrategy(); - - IRangeStrategy childRangeStrategy = isChildMaxInclusive - ? new InclusiveMaxRangeStrategy() - : new ExclusiveMaxRangeStrategy(); - - // Create range generator contexts for both parent and child using the same min and max - RangeGeneratorContext parentContext = new RangeGeneratorContext(parentRangeStrategy); - RangeGeneratorContext childContext = new RangeGeneratorContext(childRangeStrategy); - - // Generate ranges for parent and child based on shared min and max range - (string min, string max) parentRange = parentContext.GenerateRange(minRange, maxRange); - (string min, string max) childRange = childContext.GenerateRange(minRange, maxRange); - - // Note, the values of isMaxInclusive and isMinInclusive do not affect the outcome of this test, as IsSubset only depends on the min and max values of the parent and child ranges. + bool actualIsSubset = ContainerCore.IsSubset( + new Documents.Routing.Range(isMinInclusive: parentIsMinInclusive, isMaxInclusive: parentIsMaxInclusive, min: parentMinValue, max: parentMaxValue), + new Documents.Routing.Range(isMinInclusive: childIsMinInclusive, isMaxInclusive: childIsMaxInclusive, min: childMinValue, max: childMaxValue)); Assert.AreEqual( - expected: expectedIsSubsetOfParent, - actual: ContainerCore.IsSubset( - parentRange: new Documents.Routing.Range( - min: parentRange.min, - max: parentRange.max, - isMinInclusive: true, - isMaxInclusive: isParentMaxInclusive), - childRange: new Documents.Routing.Range( - min: childRange.min, - max: childRange.max, - isMinInclusive: true, - isMaxInclusive: isChildMaxInclusive))); + expected: expectedIsSubset, + actual: actualIsSubset); } /// @@ -789,42 +791,4 @@ public void GivenListOfFeedRangesEnsureConsistentInclusivityValidatesRangesTest( Assert.IsNull(exception); } } - - public interface IRangeStrategy - { - (string min, string max) GenerateRange(string min, string maxValue); - } - - public class InclusiveMaxRangeStrategy : IRangeStrategy - { - public (string min, string max) GenerateRange(string min, string maxValue) - { - return (min, maxValue); // max is inclusive - } - } - - public class ExclusiveMaxRangeStrategy : IRangeStrategy - { - public (string min, string max) GenerateRange(string min, string maxValue) - { - // Subtract 1 lexicographically for exclusive max range - string exclusiveMax = (maxValue.Length > 0) ? (char)(maxValue[0] - 1) + maxValue[1..] : maxValue; - return (min, exclusiveMax); - } - } - - public class RangeGeneratorContext - { - private readonly IRangeStrategy rangeStrategy; - - public RangeGeneratorContext(IRangeStrategy rangeStrategy) - { - this.rangeStrategy = rangeStrategy; - } - - public (string min, string max) GenerateRange(string min, string maxValue) - { - return this.rangeStrategy.GenerateRange(min, maxValue); - } - } } From 05e87a0131a62bd9d5dacc6a37baf90d089b849b Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 15:48:42 -0400 Subject: [PATCH 107/145] update summary --- .../IsFeedRangePartOfAsyncTests.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index bd7b38202a..6e14546150 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -659,15 +659,6 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// And the child feed range is inclusive of its min value /// And the child feed range is inclusive of its max value /// Then the result should be indicating if the child is a subset of the parent - /// - /// Examples: - /// | parentIsMinInclusive | parentIsMaxInclusive | parentMinValue | parentMaxValue | childIsMinInclusive | childIsMaxInclusive | childMinValue | childMaxValue | expectedIsSubset | - /// | true | true | "A" | "Z" | true | true | "A" | "Z" | true | - /// | true | false | "A" | "Y" | true | false | "A" | "Y" | true | - /// | false | true | "B" | "Z" | false | true | "B" | "Z" | true | - /// | false | false | "B" | "Y" | false | false | "B" | "Y" | true | - /// | true | true | "A" | "Z" | true | false | "A" | "Y" | true | - /// | ... | ... | ... | ... | ... | ... | ... | ... | ... | /// ]]> /// [TestMethod] From 08762029214b31c89b5a545a4fa9e12316fe7a91 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 15:51:00 -0400 Subject: [PATCH 108/145] removed unnecessary comment --- .../IsFeedRangePartOfAsyncTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 6e14546150..9cd48250f3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -645,8 +645,6 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF } } - // GivenParentRangeWhenChildRangeComparedThenValidateIfSubset - /// /// Date: Wed, 18 Sep 2024 17:23:42 -0400 Subject: [PATCH 109/145] removed some scenarios --- .../IsFeedRangePartOfAsyncTests.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 9cd48250f3..e596923faf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -663,21 +663,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [Owner("philipthomas")] [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, child is a subset")] [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] - [DataRow(true, true, "A", "Z", false, true, "B", "Z", true, DisplayName = "Given parent range is fully inclusive and child range starts exclusively after parent, but both end inclusively, child is a subset")] - [DataRow(true, true, "A", "Z", false, false, "B", "Y", true, DisplayName = "Given parent range is fully inclusive and child range is fully exclusive within the parent, child is a subset")] [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] - [DataRow(true, false, "A", "Y", false, true, "B", "Z", false, DisplayName = "Given child range exceeds the parent’s exclusive max with an inclusive max, child is not a subset")] - [DataRow(true, false, "A", "Y", false, false, "B", "Y", true, DisplayName = "Given parent range is partially inclusive and child is fully exclusive but within the parent’s bounds, child is a subset")] - [DataRow(false, true, "B", "Z", true, true, "A", "Z", false, DisplayName = "Given parent range excludes its min and starts after the child range, but both end inclusively, child is not a subset")] - [DataRow(false, true, "B", "Z", true, false, "A", "Y", false, DisplayName = "Given parent range excludes its min and child range is less than parent’s exclusive min, child is not a subset")] - [DataRow(false, true, "B", "Z", false, true, "B", "Z", false, DisplayName = "Given both parent and child ranges are exclusive at the start and inclusive at the end with the same values, child is not a subset")] - [DataRow(false, true, "B", "Z", false, false, "B", "Y", false, DisplayName = "Given parent range excludes its min and includes its max, and child is fully exclusive within the parent’s bounds, child is not a subset")] - [DataRow(false, false, "B", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range excludes both min and max, and child range is larger with inclusive bounds, child is not a subset")] - [DataRow(false, false, "B", "Y", true, false, "A", "Y", false, DisplayName = "Given parent range excludes both min and max, and child range is larger with an inclusive min, child is not a subset")] - [DataRow(false, false, "B", "Y", false, true, "B", "Z", false, DisplayName = "Given parent range excludes both bounds and child range exceeds parent’s max, child is not a subset")] - [DataRow(false, false, "B", "Y", false, false, "B", "Y", false, DisplayName = "Given both parent and child ranges are fully exclusive and equal, child is not a subset")] - public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From fb7b942a1316b53eed24db893ec556c83a05d120 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 17:26:37 -0400 Subject: [PATCH 110/145] add PREVIEW flag --- .../src/Resource/Container/Container.cs | 8 +++++++- .../src/Resource/Container/ContainerCore.Items.cs | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index e1bd3b3263..796b52f916 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1781,7 +1781,13 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the child feed range is a subset of the parent feed range; otherwise, false. - public virtual Task IsFeedRangePartOfAsync( +#if PREVIEW + public +#else + internal +#endif + + virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index b0609925ca..a1c8d6c266 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1257,7 +1257,12 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } - public override async Task IsFeedRangePartOfAsync( +#if PREVIEW + public +#else + internal +#endif + override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) From 7868ef61c8d7b17fc346b97a239904ed66075df6 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 17:34:37 -0400 Subject: [PATCH 111/145] updatecontracts --- .../Contracts/DotNetPreviewSDKAPI.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 64eb748af9..17523aa5c9 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 @@ -326,7 +326,7 @@ "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[System.String]] GetPartitionKeyRangesAsync(Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", From acbad19d1778faea163ef72203a3b5dda49695b3 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 18:47:04 -0400 Subject: [PATCH 112/145] include more scenarios --- .../IsFeedRangePartOfAsyncTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index e596923faf..d0af195fcc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -665,6 +665,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From 6140a90001dd406b90e0a27127268992fb8738e0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 18:47:42 -0400 Subject: [PATCH 113/145] add verbage --- .../IsFeedRangePartOfAsyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index d0af195fcc..a573bb6420 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -666,7 +666,7 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From f2353788ad39cc9120632b2049bcc4a854df0c89 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 18 Sep 2024 20:13:41 -0400 Subject: [PATCH 114/145] add IsFeedRangePartofAsync in ContainerCore. --- .../Resource/Container/ContainerInlineCore.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 2f9184527e..39a695eca2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -674,5 +674,21 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), openTelemetry: new (OpenTelemetryConstants.Operations.DeleteAllItemsByPartitionKey, (response) => new OpenTelemetryResponse(response))); } + +#if PREVIEW + public +#else + internal +#endif + override Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default) + { + return base.IsFeedRangePartOfAsync( + parentFeedRange, + childFeedRange, + cancellationToken); + } } } \ No newline at end of file From a7f048aeb8ec4d03db6760f7f7b173808e55f4b9 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 19 Sep 2024 02:40:38 -0400 Subject: [PATCH 115/145] new scenarios to illustrate my point in conversation --- .../IsFeedRangePartOfAsyncTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index a573bb6420..2641235751 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -667,6 +667,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] + [DataRow(true, false, "", "1FF", true, true, "", "FF", false)] + [DataRow(true, false, "", "FF", true, true, "", "FF", true)] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From 8497db9e8ab6164c4219fc57c3800a70b4fa4f36 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 19 Sep 2024 09:00:24 -0400 Subject: [PATCH 116/145] add DisplayName for 2 DataRows --- .../IsFeedRangePartOfAsyncTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 2641235751..abd13b4a5f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -667,8 +667,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] - [DataRow(true, false, "", "1FF", true, true, "", "FF", false)] - [DataRow(true, false, "", "FF", true, true, "", "FF", true)] + [DataRow(true, false, "", "1FF", true, true, "", "FF", false, DisplayName = "Given parent range has an exclusive max and child range is fully inclusive with a different max, child is not a subset")] + [DataRow(true, false, "", "FF", true, true, "", "FF", true, DisplayName = "Given both parent and child ranges are equal and fully inclusive with exclusive max, child is a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From 35acb5f56ef0f0146c1b0d715e91ba839662a202 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 19 Sep 2024 10:42:12 -0400 Subject: [PATCH 117/145] since name changed from IsSubset to IsFeedRangePartOfAsync, fixing trace name --- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index a1c8d6c266..7d1dcccbd5 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1267,7 +1267,7 @@ override async Task IsFeedRangePartOfAsync( FeedRange childFeedRange, CancellationToken cancellationToken = default) { - using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsSubset Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) + using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsFeedRangePartOfAsync Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) { if (parentFeedRange == null || childFeedRange == null) { From 1b17ab7eadff3dabad3545058b0f27814751f6bd Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 19 Sep 2024 11:58:02 -0400 Subject: [PATCH 118/145] trying Diagram 2 with ContainerInternal instead of ContainerInlineCore --- .../src/Resource/Container/Container.cs | 11 +++-------- .../Resource/Container/ContainerCore.Items.cs | 7 +------ .../Resource/Container/ContainerInlineCore.cs | 16 ---------------- .../src/Resource/Container/ContainerInternal.cs | 5 +++++ 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 796b52f916..5dcd1b5cbd 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1755,7 +1755,7 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); -#endif + /// /// Determines whether the given child feed range is a subset of the specified parent feed range. /// @@ -1781,18 +1781,13 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the child feed range is a subset of the parent feed range; otherwise, false. -#if PREVIEW - public -#else - internal -#endif - - virtual Task IsFeedRangePartOfAsync( + public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } +#endif } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 7d1dcccbd5..27a63886c1 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1257,12 +1257,7 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } -#if PREVIEW - public -#else - internal -#endif - override async Task IsFeedRangePartOfAsync( + public override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, CancellationToken cancellationToken = default) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 39a695eca2..2f9184527e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -674,21 +674,5 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), openTelemetry: new (OpenTelemetryConstants.Operations.DeleteAllItemsByPartitionKey, (response) => new OpenTelemetryResponse(response))); } - -#if PREVIEW - public -#else - internal -#endif - override Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, - CancellationToken cancellationToken = default) - { - return base.IsFeedRangePartOfAsync( - parentFeedRange, - childFeedRange, - cancellationToken); - } } } \ 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 cedef3f3de..acb85f95a3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -151,6 +151,11 @@ public abstract Task> GetPartitionKeyRangesAsync( public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( string processorName, ChangeFeedHandler> onChangesDelegate); + + public abstract Task IsFeedRangePartOfAsync( + Cosmos.FeedRange parentFeedRange, + Cosmos.FeedRange childFeedRange, + CancellationToken cancellationToken = default); #endif public abstract class TryExecuteQueryResult From 76940cc5c46899929285eef2e1d4d0a4cce982c0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 19 Sep 2024 16:17:38 -0400 Subject: [PATCH 119/145] fix to IsSub and test that are affected. --- .../Resource/Container/ContainerCore.Items.cs | 60 ++++++++++++------- .../IsFeedRangePartOfAsyncTests.cs | 60 ++++++++++++++++--- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 27a63886c1..c07c4f6686 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1398,31 +1398,51 @@ internal static void EnsureConsistentInclusivity(List - /// Determines whether the specified child range is entirely within the bounds of the parent range. - /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. - /// - /// The parent range to check against, representing the outer bounds. - /// The child range being evaluated for subset inclusion within the parent range. - /// - /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); - /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). - /// ]]> - /// - /// - /// Returns true if the child range is a subset of the parent range, meaning the child range's - /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. - /// + /// + /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. + /// + /// Additionally, the method performs null checks on the parameters: + /// - If is null, an is thrown. + /// - If is null, an is thrown. + /// + /// The parent range to check against, representing the outer bounds. Cannot be null. + /// The child range being evaluated for subset inclusion within the parent range. Cannot be null. + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// ]]> + /// + /// + /// Returns true if the child range is a subset of the parent range, meaning the child range's + /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// + /// + /// Thrown when or is null. + /// internal static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) { + if (parentRange is null) + { + throw new ArgumentNullException(nameof(parentRange)); + } + + if (childRange is null) + { + throw new ArgumentNullException(nameof(childRange)); + } + + bool isMaxWithinParent = !parentRange.IsMaxInclusive + ? parentRange.Contains(childRange.Max) + : parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); + bool isMinWithinParent = parentRange.Contains(childRange.Min); - bool isMaxWithinParent = parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); return isMinWithinParent && isMaxWithinParent; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index abd13b4a5f..6babdb17aa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -410,13 +410,13 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxI yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, false }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. } @@ -458,13 +458,13 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, false }; // "The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, false }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. } /// @@ -664,11 +664,10 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, child is a subset")] [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "Y", false, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is not a subset")] [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] - [DataRow(true, false, "", "1FF", true, true, "", "FF", false, DisplayName = "Given parent range has an exclusive max and child range is fully inclusive with a different max, child is not a subset")] - [DataRow(true, false, "", "FF", true, true, "", "FF", true, DisplayName = "Given both parent and child ranges are equal and fully inclusive with exclusive max, child is a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max and child range is fully inclusive, and both have the same min and max values, child is not a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, @@ -689,6 +688,49 @@ public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( actual: actualIsSubset); } + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public void GivenNullParentFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() + { + ArgumentNullException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( + parentRange: null, + childRange: new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true))); + + Assert.IsNotNull(exception); + } + + /// + /// + /// + [TestMethod] + [Owner("philipthomas-MSFT")] + public void GivenNullChildFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() + { + ArgumentNullException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( + parentRange: new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true), + childRange: null)); + + Assert.IsNotNull(exception); + } + /// /// Validates if all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive. /// Throws InvalidOperationException if any inconsistencies are found. From 10898e0c16cbf3622421a2c4977cb32a88e5e16f Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 20 Sep 2024 08:56:38 -0400 Subject: [PATCH 120/145] overriding in ContainerInlineCore, but not using operations helper, etc. --- .../src/Resource/Container/ContainerInlineCore.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 2f9184527e..335fc7b296 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -674,5 +674,16 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( task: (trace) => base.DeleteAllItemsByPartitionKeyStreamAsync(partitionKey, trace, requestOptions, cancellationToken), openTelemetry: new (OpenTelemetryConstants.Operations.DeleteAllItemsByPartitionKey, (response) => new OpenTelemetryResponse(response))); } + + public override Task IsFeedRangePartOfAsync( + FeedRange parentFeedRange, + FeedRange childFeedRange, + CancellationToken cancellationToken = default) + { + return base.IsFeedRangePartOfAsync( + parentFeedRange: parentFeedRange, + childFeedRange: childFeedRange, + cancellationToken: cancellationToken); + } } } \ No newline at end of file From 8790e7bb87fbb7e8970846533ef2cab055132fe0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Sat, 21 Sep 2024 00:50:21 -0400 Subject: [PATCH 121/145] fix test implementation for IsSubset. add more DataRows --- .../src/Resource/Container/Container.cs | 81 +++++--- .../Resource/Container/ContainerCore.Items.cs | 180 +++++++++++++----- .../IsFeedRangePartOfAsyncTests.cs | 24 +-- 3 files changed, 197 insertions(+), 88 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 5dcd1b5cbd..6ffe07aeb2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1756,31 +1756,62 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV string processorName, ChangeFeedHandler> onChangesDelegate); - /// - /// Determines whether the given child feed range is a subset of the specified parent feed range. - /// - /// The feed range representing the parent range. - /// The feed range representing the child range. - /// A token to cancel the operation if needed. - /// - /// - /// - /// - /// - /// True if the child feed range is a subset of the parent feed range; otherwise, false. + /// + /// Determines whether the given child feed range is a part of the specified parent feed range. + /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. + /// + /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. + /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. + /// + /// - **Validation and Parsing**: + /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. + /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). + /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. + /// + /// - **Partition Key and Routing Map Setup**: + /// - The partition key definition for the container is retrieved asynchronously using `GetPartitionKeyDefinitionAsync`, as it is required to identify the partition structure. + /// - The method also retrieves the container's resource ID (`containerRId`) and the partition key range routing map from the `IRoutingMapProvider`. These are essential for determining the actual partition key ranges that correspond to the feed ranges. + /// + /// - **Effective Ranges**: + /// - The method uses `GetEffectiveRangesAsync` to retrieve the actual ranges of partition keys that each feed range represents. + /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. + /// + /// - **Inclusivity Consistency**: + /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. + /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. + /// + /// - **Subset Check**: + /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. + /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. + /// + /// - **Exception Handling**: + /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. + /// + /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. + /// + /// + /// The feed range representing the parent range. + /// The feed range representing the child range. + /// A token to cancel the operation if needed. + /// + /// + /// + /// + /// + /// True if the child feed range is a subset of the parent feed range; otherwise, false. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange parentFeedRange, Cosmos.FeedRange childFeedRange, diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index c07c4f6686..f5469f73c2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1325,14 +1325,45 @@ public override async Task IsFeedRangePartOfAsync( } /// - /// Merges a list of feed ranges into a single range by taking the minimum of the first range and the maximum of the last range. - /// If only one range exists, it returns that range. + /// Merges a list of feed ranges into a single range by taking the minimum value of the first range and the maximum value of the last range. + /// This function ensures that the resulting range covers the entire span of the input ranges. + /// + /// - The method begins by checking if the list contains only one range: + /// - If there is only one range, it simply returns that range without performing any additional logic. + /// + /// - If the list contains multiple ranges: + /// - It first sorts the ranges based on the minimum value of each range using a custom comparator (`MinComparer`). + /// - It selects the first range (after sorting) to extract the minimum value, ensuring the merged range starts with the lowest value across all ranges. + /// - It selects the last range (after sorting) to extract the maximum value, ensuring the merged range ends with the highest value across all ranges. + /// + /// - The inclusivity of the boundaries (`IsMinInclusive` and `IsMaxInclusive`) is inherited from the first range in the list: + /// - `IsMinInclusive` from the first range determines whether the merged range includes its minimum value. + /// - `IsMaxInclusive` from the last range would generally be expected to influence whether the merged range includes its maximum value, but this method uses `IsMaxInclusive` from the first range for both boundaries. + /// - **Note**: This could result in unexpected behavior if inclusivity should differ for the merged max value. + /// + /// - The merged range spans the minimum value of the first range and the maximum value of the last range, effectively combining multiple ranges into a single, continuous range. /// - /// The list of feed ranges to merge. + /// The list of feed ranges to merge. Each range contains a minimum and maximum value along with boundary inclusivity flags (`IsMinInclusive`, `IsMaxInclusive`). /// /// A new merged range with the minimum value from the first range and the maximum value from the last range. - /// If the list contains a single range, it returns that range. - /// + /// If the list contains a single range, it returns that range directly without modification. + /// + /// + /// Thrown when the list of ranges is empty. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range("A", "C", true, false), + /// new Documents.Routing.Range("D", "F", true, true), + /// new Documents.Routing.Range("G", "I", false, true), + /// }; + /// + /// Documents.Routing.Range mergedRange = MergeRanges(ranges); + /// // The merged range would span from "A" to "I", taking the minimum of the first range and the maximum of the last. + /// ]]> + /// private static Documents.Routing.Range MergeRanges( List> ranges) { @@ -1353,26 +1384,38 @@ private static Documents.Routing.Range MergeRanges( isMaxInclusive: firstRange.IsMaxInclusive); } - /// - /// Validates whether all ranges in the list have consistent inclusivity for both IsMinInclusive and IsMaxInclusive boundaries. - /// Throws an InvalidOperationException if any inconsistencies are found across the ranges. - /// - /// The list of ranges to validate. - /// - /// Thrown when 'IsMinInclusive' or 'IsMaxInclusive' values are inconsistent across ranges. - /// - /// - /// > ranges = new List> - /// { - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } - /// }; - /// + /// + /// Validates whether all ranges in the list have consistent inclusivity for both `IsMinInclusive` and `IsMaxInclusive` boundaries. + /// This ensures that all ranges either have the same inclusivity or exclusivity for their minimum and maximum boundaries. + /// If there are any inconsistencies in the inclusivity/exclusivity of the ranges, it throws an `InvalidOperationException`. + /// + /// The logic works as follows: + /// - The method starts by checking the first range in the list to establish a baseline for comparison. + /// - It then iterates over the remaining ranges, comparing their `IsMinInclusive` and `IsMaxInclusive` values with those of the first range. + /// - If any range differs from the first in terms of inclusivity or exclusivity (either for the min or max boundary), the method sets a flag (`areAnyDifferent`) and exits the loop early. + /// - If any differences are found, the method gathers the distinct `IsMinInclusive` and `IsMaxInclusive` values found across all ranges. + /// - It then throws an `InvalidOperationException`, including the distinct values in the exception message to indicate the specific inconsistencies. + /// + /// This method is useful in scenarios where the ranges need to have uniform inclusivity for boundary conditions. + /// + /// The list of ranges to validate. Each range has `IsMinInclusive` and `IsMaxInclusive` values that represent the inclusivity of its boundaries. + /// + /// Thrown when `IsMinInclusive` or `IsMaxInclusive` values are inconsistent across ranges. The exception message includes details of the inconsistencies. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } + /// }; + /// /// EnsureConsistentInclusivity(ranges); - /// ]]> + /// + /// // This will throw an InvalidOperationException because there are different inclusivity values for IsMinInclusive and IsMaxInclusive. + /// ]]> /// internal static void EnsureConsistentInclusivity(List> ranges) { @@ -1398,31 +1441,59 @@ internal static void EnsureConsistentInclusivity(List - /// Determines whether the specified child range is entirely within the bounds of the parent range. - /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. - /// - /// Additionally, the method performs null checks on the parameters: - /// - If is null, an is thrown. - /// - If is null, an is thrown. - /// - /// The parent range to check against, representing the outer bounds. Cannot be null. - /// The child range being evaluated for subset inclusion within the parent range. Cannot be null. - /// - /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); - /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). - /// ]]> - /// - /// - /// Returns true if the child range is a subset of the parent range, meaning the child range's - /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. - /// - /// - /// Thrown when or is null. + /// + /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. + /// + /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, + /// taking into account whether each boundary is inclusive or exclusive. + /// + /// - For the `Max` boundary: + /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. + /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. + /// - This applies to the following combinations: + /// - (false, false): Both max values are exclusive. + /// - (true, true): Both max values are inclusive. + /// - (true, false): Parent max is inclusive, child max is exclusive. + /// + /// - For the `Min` boundary: + /// - It simply checks whether the parent range contains the child range's min value. + /// + /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. + /// + /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: + /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: + /// - The parent range is exclusive at max, but the child range is inclusive. + /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: + /// - Both ranges are exclusive at max. + /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: + /// - Both ranges are inclusive at max. + /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: + /// - The parent range is inclusive at max, but the child range is exclusive. + /// + /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. + /// + /// Additionally, the method performs null checks on the parameters: + /// - If is null, an is thrown. + /// - If is null, an is thrown. + /// + /// The parent range to check against, representing the outer bounds. Cannot be null. + /// The child range being evaluated for subset inclusion within the parent range. Cannot be null. + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// ]]> + /// + /// + /// Returns true if the child range is a subset of the parent range, meaning the child range's + /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// + /// + /// Thrown when or is null. /// internal static bool IsSubset( Documents.Routing.Range parentRange, @@ -1438,9 +1509,14 @@ internal static bool IsSubset( throw new ArgumentNullException(nameof(childRange)); } - bool isMaxWithinParent = !parentRange.IsMaxInclusive - ? parentRange.Contains(childRange.Max) - : parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max); + bool isMaxWithinParent = (parentRange.IsMaxInclusive, childRange.IsMaxInclusive) switch + { + (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive + _ => parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max) // Default for the following combinations: + // (false, false): Both max values are exclusive + // (true, true): Both max values are inclusive + // (true, false): Parent max is inclusive, child max is exclusive + }; bool isMinWithinParent = parentRange.Contains(childRange.Min); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 6babdb17aa..16f4b24da4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -410,13 +410,13 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxI yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, false }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, does fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. } @@ -660,14 +660,16 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// ]]> /// [TestMethod] - [Owner("philipthomas")] - [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, child is a subset")] - [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] - [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "Y", false, DisplayName = "Given both parent and child ranges share an inclusive min and exclusive max, child is not a subset")] - [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] - [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "Given parent range has an exclusive max and child range is fully inclusive, and both have the same min and max values, child is not a subset")] + [Owner("philipthomas-MSFT")] + [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, child is a subset")] + [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "(true, false) Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] + [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "(false, false) Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range has an exclusive max and child range is fully inclusive, and both have the same min and max values, child is not a subset")] + [DataRow(true, false, "A", "W", true, false, "A", "Y", false, DisplayName = "(false, false) Given both parent and child ranges have inclusive min and exclusive max, and child’s max is less than parent’s max, child is a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "W", true, DisplayName = "(false, false) Given both parent and child ranges have inclusive min and exclusive max, and child’s max is less than parent’s max, child is a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From 3d8d306b8aae1244425b900c1cd5864fb3053d3e Mon Sep 17 00:00:00 2001 From: philipthomas Date: Sat, 21 Sep 2024 06:42:32 -0400 Subject: [PATCH 122/145] issue with summary --- Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 6ffe07aeb2..7e46a909c4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1788,7 +1788,6 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. /// /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. - /// /// /// The feed range representing the parent range. /// The feed range representing the child range. From 5bbad5128f7d46f834fdf43ca82556e3f7a0df39 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Sat, 21 Sep 2024 10:05:22 -0400 Subject: [PATCH 123/145] removed Range from summary --- .../src/Resource/Container/Container.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 7e46a909c4..48447b065f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1756,38 +1756,38 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV string processorName, ChangeFeedHandler> onChangesDelegate); - /// - /// Determines whether the given child feed range is a part of the specified parent feed range. - /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. - /// - /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. - /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. - /// - /// - **Validation and Parsing**: - /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. - /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). - /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. - /// - /// - **Partition Key and Routing Map Setup**: - /// - The partition key definition for the container is retrieved asynchronously using `GetPartitionKeyDefinitionAsync`, as it is required to identify the partition structure. - /// - The method also retrieves the container's resource ID (`containerRId`) and the partition key range routing map from the `IRoutingMapProvider`. These are essential for determining the actual partition key ranges that correspond to the feed ranges. - /// - /// - **Effective Ranges**: - /// - The method uses `GetEffectiveRangesAsync` to retrieve the actual ranges of partition keys that each feed range represents. - /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. - /// - /// - **Inclusivity Consistency**: - /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. - /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. - /// - /// - **Subset Check**: - /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. - /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. - /// - /// - **Exception Handling**: - /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. - /// - /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. + /// + /// Determines whether the given child feed range is a part of the specified parent feed range. + /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. + /// + /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. + /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. + /// + /// - **Validation and Parsing**: + /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. + /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). + /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. + /// + /// - **Partition Key and Routing Map Setup**: + /// - The partition key definition for the container is retrieved asynchronously using `GetPartitionKeyDefinitionAsync`, as it is required to identify the partition structure. + /// - The method also retrieves the container's resource ID (`containerRId`) and the partition key range routing map from the `IRoutingMapProvider`. These are essential for determining the actual partition key ranges that correspond to the feed ranges. + /// + /// - **Effective Ranges**: + /// - The method uses `GetEffectiveRangesAsync` to retrieve the actual ranges of partition keys that each feed range represents. + /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. + /// + /// - **Inclusivity Consistency**: + /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. + /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. + /// + /// - **Subset Check**: + /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. + /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. + /// + /// - **Exception Handling**: + /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. + /// + /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. /// /// The feed range representing the parent range. /// The feed range representing the child range. From 07a294b10c5735836d9cc53b586380a22e156768 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 23 Sep 2024 11:44:05 -0400 Subject: [PATCH 124/145] add more scenarios and reorganized the DataRows --- .../IsFeedRangePartOfAsyncTests.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 16f4b24da4..482cc52dc4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -661,15 +661,19 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, child is a subset")] - [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "(true, false) Given parent range is fully inclusive and child range has an exclusive max, child is a subset")] - [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range has an exclusive max but child range exceeds the parent’s max with an inclusive bound, child is not a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "(false, false) Given both parent and child ranges share an inclusive min and exclusive max, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, and min and max range is the same, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive and equal, and min and max range are not the same, child is not a subset")] - [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range has an exclusive max and child range is fully inclusive, and both have the same min and max values, child is not a subset")] - [DataRow(true, false, "A", "W", true, false, "A", "Y", false, DisplayName = "(false, false) Given both parent and child ranges have inclusive min and exclusive max, and child’s max is less than parent’s max, child is a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "W", true, DisplayName = "(false, false) Given both parent and child ranges have inclusive min and exclusive max, and child’s max is less than parent’s max, child is a subset")] + [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both parent and child ranges (A to Z) are fully inclusive and equal, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both parent and child ranges (A to A) are fully inclusive and equal, and min and max range is the same, child is a subset")] + [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive but min and max ranges are not the same (A to A, B to B), child is not a subset")] + [DataRow(true, true, "B", "B", true, true, "A", "A", false, DisplayName = "(true, true) Given parent range (B to B) is fully inclusive and child range (A to A) is fully inclusive, child is not a subset")] + [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "(true, false) Given parent range (A to Z) is fully inclusive and child range (A to Y) has an exclusive max, child is a subset")] + [DataRow(true, true, "A", "Y", true, false, "A", "Z", false, DisplayName = "(true, false) Given parent range (A to Y) is fully inclusive and child range (A to Z) has an exclusive max, child is not a subset")] + [DataRow(true, true, "A", "Z", true, false, "A", "Z", true, DisplayName = "(true, false) Given parent range (A to Z) is fully inclusive and child range (A to Z) has an exclusive max, child is a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Y", true, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Y) is fully inclusive, child is a subset")] + [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Y) has an exclusive max but child range (A to Z) exceeds the parent’s max with an inclusive bound, child is not a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Z) is fully inclusive, child is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "(false, false) Given parent range (A to Y) is inclusive at min and exclusive at max, and child range (A to Y) is inclusive at min and exclusive at max, child is a subset")] + [DataRow(true, false, "A", "W", true, false, "A", "Y", false, DisplayName = "(false, false) Given parent range (A to W) is inclusive at min and exclusive at max, and child range (A to Y) is inclusive at min and exclusive at max, child is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "W", true, DisplayName = "(false, false) Given parent range (A to Y) is inclusive at min and exclusive at max, and child range (A to W) is inclusive at min and exclusive at max, child is a subset")] public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( bool parentIsMinInclusive, bool parentIsMaxInclusive, From 5bb871b66aef3744e28366e37d07fe87d08d220a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 23 Sep 2024 13:12:51 -0400 Subject: [PATCH 125/145] move some documentation around --- .../src/Resource/Container/Container.cs | 29 ------------------ .../Resource/Container/ContainerCore.Items.cs | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 48447b065f..744e223905 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1759,35 +1759,6 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// Determines whether the given child feed range is a part of the specified parent feed range. /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. - /// - /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. - /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. - /// - /// - **Validation and Parsing**: - /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. - /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). - /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. - /// - /// - **Partition Key and Routing Map Setup**: - /// - The partition key definition for the container is retrieved asynchronously using `GetPartitionKeyDefinitionAsync`, as it is required to identify the partition structure. - /// - The method also retrieves the container's resource ID (`containerRId`) and the partition key range routing map from the `IRoutingMapProvider`. These are essential for determining the actual partition key ranges that correspond to the feed ranges. - /// - /// - **Effective Ranges**: - /// - The method uses `GetEffectiveRangesAsync` to retrieve the actual ranges of partition keys that each feed range represents. - /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. - /// - /// - **Inclusivity Consistency**: - /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. - /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. - /// - /// - **Subset Check**: - /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. - /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. - /// - /// - **Exception Handling**: - /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. - /// - /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. /// /// The feed range representing the parent range. /// The feed range representing the child range. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index f5469f73c2..a6b8243cec 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1257,6 +1257,36 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } + /// + /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. + /// + /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. + /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. + /// + /// - **Validation and Parsing**: + /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. + /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). + /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. + /// + /// - **Partition Key and Routing Map Setup**: + /// - The partition key definition for the container is retrieved asynchronously using `GetPartitionKeyDefinitionAsync`, as it is required to identify the partition structure. + /// - The method also retrieves the container's resource ID (`containerRId`) and the partition key range routing map from the `IRoutingMapProvider`. These are essential for determining the actual partition key ranges that correspond to the feed ranges. + /// + /// - **Effective Ranges**: + /// - The method uses `GetEffectiveRangesAsync` to retrieve the actual ranges of partition keys that each feed range represents. + /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. + /// + /// - **Inclusivity Consistency**: + /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. + /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. + /// + /// - **Subset Check**: + /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. + /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. + /// + /// - **Exception Handling**: + /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. + /// public override async Task IsFeedRangePartOfAsync( FeedRange parentFeedRange, FeedRange childFeedRange, From a6d4eda9e3ad685ad0c4c74882b7c9fd2012ab08 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 23 Sep 2024 14:41:11 -0400 Subject: [PATCH 126/145] wrap async method in OperationHelperAsync for consistency --- .../src/Resource/Container/ContainerInlineCore.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 335fc7b296..96758c11ca 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -680,10 +680,16 @@ public override Task IsFeedRangePartOfAsync( FeedRange childFeedRange, CancellationToken cancellationToken = default) { - return base.IsFeedRangePartOfAsync( - parentFeedRange: parentFeedRange, - childFeedRange: childFeedRange, - cancellationToken: cancellationToken); + return this.ClientContext.OperationHelperAsync( + operationName: nameof(IsFeedRangePartOfAsync), + containerName: this.Id, + databaseName: this.Database.Id, + operationType: Documents.OperationType.ReadFeed, + requestOptions: null, + task: (trace) => base.IsFeedRangePartOfAsync( + parentFeedRange: parentFeedRange, + childFeedRange: childFeedRange, + cancellationToken: cancellationToken)); } } } \ No newline at end of file From de79711dddc0084112244b291ef1d13d89f7c59a Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 23 Sep 2024 14:53:23 -0400 Subject: [PATCH 127/145] add TODO about Documents.OperationType.ReadFeed on operations not related to it. --- .../src/Resource/Container/ContainerInlineCore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 96758c11ca..4b3450afaa 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -577,6 +577,9 @@ public override TransactionalBatch CreateTransactionalBatch(PartitionKey partiti public override Task> GetFeedRangesAsync(CancellationToken cancellationToken = default) { + // TODO: The current use of Documents.OperationType.ReadFeed is not a precise fit for this operation. + // A more suitable or generic Documents.OperationType should be created in the future to accurately represent this action. + return this.ClientContext.OperationHelperAsync( operationName: nameof(GetFeedRangesAsync), containerName: this.Id, @@ -680,6 +683,9 @@ public override Task IsFeedRangePartOfAsync( FeedRange childFeedRange, CancellationToken cancellationToken = default) { + // TODO: The current use of Documents.OperationType.ReadFeed is not a precise fit for this operation. + // A more suitable or generic Documents.OperationType should be created in the future to accurately represent this action. + return this.ClientContext.OperationHelperAsync( operationName: nameof(IsFeedRangePartOfAsync), containerName: this.Id, From e5846c9254225521114000542ca462d582c824b5 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 24 Sep 2024 14:42:47 -0400 Subject: [PATCH 128/145] ranges is null empty so just pull the first one. also update summary. --- .../Resource/Container/ContainerCore.Items.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index a6b8243cec..628b5e58b2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1418,9 +1418,10 @@ private static Documents.Routing.Range MergeRanges( /// Validates whether all ranges in the list have consistent inclusivity for both `IsMinInclusive` and `IsMaxInclusive` boundaries. /// This ensures that all ranges either have the same inclusivity or exclusivity for their minimum and maximum boundaries. /// If there are any inconsistencies in the inclusivity/exclusivity of the ranges, it throws an `InvalidOperationException`. - /// + /// /// The logic works as follows: - /// - The method starts by checking the first range in the list to establish a baseline for comparison. + /// - The method assumes that the `ranges` list is never null. + /// - It starts by checking the first range in the list to establish a baseline for comparison. /// - It then iterates over the remaining ranges, comparing their `IsMinInclusive` and `IsMaxInclusive` values with those of the first range. /// - If any range differs from the first in terms of inclusivity or exclusivity (either for the min or max boundary), the method sets a flag (`areAnyDifferent`) and exits the loop early. /// - If any differences are found, the method gathers the distinct `IsMinInclusive` and `IsMaxInclusive` values found across all ranges. @@ -1451,15 +1452,14 @@ internal static void EnsureConsistentInclusivity(List firstRange = ranges[0]; + + foreach (Documents.Routing.Range range in ranges.Skip(1)) { - foreach (Documents.Routing.Range range in ranges.Skip(1)) + if (range.IsMinInclusive != firstRange.IsMinInclusive || range.IsMaxInclusive != firstRange.IsMaxInclusive) { - if (range.IsMinInclusive != firstRange.IsMinInclusive || range.IsMaxInclusive != firstRange.IsMaxInclusive) - { - areAnyDifferent = true; - break; - } + areAnyDifferent = true; + break; } } From 6a5d8e7d9e29bca75e8fe6060936e2e34fb07dbc Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 30 Sep 2024 08:55:39 -0400 Subject: [PATCH 129/145] add this scenario "AA", "AA", true, "", "AA", false, false --- .../IsFeedRangePartOfAsyncTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 482cc52dc4..6066cfaed1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -512,6 +512,7 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "AA", "AA", true, "", "AA", false, false }; // The child range, which starts and ends at AA (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before AA (non-inclusive), due to the parent's non-inclusive upper boundary. } /// From 56775bb050d9155d6c245451250238e9854aea3d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 30 Sep 2024 09:03:12 -0400 Subject: [PATCH 130/145] adding scenario "AA", "AA", true, "AA", "AZ", false, true --- .../IsFeedRangePartOfAsyncTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 6066cfaed1..8a723163bc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -487,6 +487,7 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "AA", "AA", true, "AA", "AZ", false, true }; // The child range, which starts and ends at AA (inclusive), fits entirely within the parent range, which starts at AA and ends just before AZ (non-inclusive). } /// From bed52de88daaab047d0cf70d6746467477730444 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 30 Sep 2024 10:29:47 -0400 Subject: [PATCH 131/145] all recent new scenarios --- .../IsFeedRangePartOfAsyncTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 8a723163bc..78066cf41c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -487,7 +487,8 @@ private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMax yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "AA", "AA", true, "AA", "AZ", false, true }; // The child range, which starts and ends at AA (inclusive), fits entirely within the parent range, which starts at AA and ends just before AZ (non-inclusive). + yield return new object[] { "AA", "AA", true, "", "AA", false, false }; // The child range, which starts and ends at AA (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before AA (non-inclusive), due to the parent's non-inclusive upper boundary. + yield return new object[] { "AA", "AA", true, "AA", "BB", false, true }; // The child range, which starts and ends at AA (inclusive), fits entirely within the parent range, which starts at AA and ends just before BB (non-inclusive), due to the child's inclusive boundary at AA. } /// @@ -513,7 +514,8 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "AA", "AA", true, "", "AA", false, false }; // The child range, which starts and ends at AA (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before AA (non-inclusive), due to the parent's non-inclusive upper boundary. + yield return new object[] { "10", "11", false, "10", "10", true, false }; // The child range, which starts at 10 and ends just before 11 (non-inclusive), does not fits entirely within the parent range, which starts and ends at 10 (inclusive), due to the parent's inclusive boundary at 10. + yield return new object[] { "A", "B", false, "A", "A", true, false }; // The child range, which starts at A and ends just before B (non-inclusive), does not fits entirely within the parent range, which starts and ends at A (inclusive), due to the parent's inclusive boundary at A. } /// From 8e008fb65418cc441dade88bdbb98b1cecd811dd Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 30 Sep 2024 13:15:20 -0400 Subject: [PATCH 132/145] made a change for isSubset to substract the max if MaxExclusive. specifically to fix this scenario "10", "11", false, "10", "10", true, true --- .../Resource/Container/ContainerCore.Items.cs | 96 +++++++++++++++++-- .../IsFeedRangePartOfAsyncTests.cs | 8 +- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 628b5e58b2..8775070325 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -9,7 +9,8 @@ namespace Microsoft.Azure.Cosmos using System.Diagnostics; using System.Globalization; using System.IO; - using System.Linq; + using System.Linq; + using System.Numerics; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -1307,7 +1308,8 @@ public override async Task IsFeedRangePartOfAsync( { if (!FeedRangeInternal.TryParse(parentFeedRange.ToJsonString(), out parentFeedRangeInternal)) { - throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, parentFeedRange.ToJsonString())); + throw new ArgumentException( + string.Format("The provided string, '{0}', for '{1}', does not represent any known format.", parentFeedRange.ToJsonString(), nameof(parentFeedRange))); } } @@ -1315,7 +1317,8 @@ public override async Task IsFeedRangePartOfAsync( { if (!FeedRangeInternal.TryParse(childFeedRange.ToJsonString(), out childFeedRangeInternal)) { - throw new ArgumentException(string.Format(ClientResources.FeedToken_UnknownFormat, childFeedRange.ToJsonString())); + throw new ArgumentException( + string.Format("The provided string, '{0}', for '{1}', does not represent any known format.", childFeedRange.ToJsonString(), nameof(childFeedRange))); } } @@ -1541,16 +1544,93 @@ internal static bool IsSubset( bool isMaxWithinParent = (parentRange.IsMaxInclusive, childRange.IsMaxInclusive) switch { - (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive - _ => parentRange.Max == childRange.Max || parentRange.Contains(childRange.Max) // Default for the following combinations: - // (false, false): Both max values are exclusive - // (true, true): Both max values are inclusive - // (true, false): Parent max is inclusive, child max is exclusive + (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive + (true, false) => ContainerCore.IsChildMaxExclusiveWithinParent(parentRange, childRange), // (true, false): Parent max is inclusive, child max is exclusive + _ => ContainerCore.IsChildMaxWithinParent(parentRange, childRange.Max) // Default for the following combinations: + // (true, true): Both max values are inclusive + // (false, false): Both max values are exclusive }; bool isMinWithinParent = parentRange.Contains(childRange.Min); return isMinWithinParent && isMaxWithinParent; } + + /// + /// Determines whether the given maximum value of the child range is either equal to or contained within the parent range. + /// + /// The parent range to compare against, which defines the boundary. + /// The maximum value of the child range to be checked. + /// True if the maximum value of the child range is equal to or contained within the parent range; otherwise, false. + private static bool IsChildMaxWithinParent( + Documents.Routing.Range parentRange, + string childRangeMax) + { + return parentRange.Max == childRangeMax || parentRange.Contains(childRangeMax); + } + + /// + /// Determines whether the exclusive maximum of the child range is either equal to or contained within the parent range. + /// + /// The range representing the parent, which is compared against. + /// The range representing the child, whose exclusive maximum is checked. + /// True if the exclusive maximum of the child range is within or equal to the parent range's maximum; otherwise, false. + private static bool IsChildMaxExclusiveWithinParent( + Documents.Routing.Range parentRange, + Documents.Routing.Range childRange) + { + // Calculate the exclusive maximum of the child range + string childMaxExclusive = ContainerCore.GetExclusiveMaxValue(childRange.Max); + + // Check if the parent's max is equal to or contains the child's exclusive max + return ContainerCore.IsChildMaxWithinParent(parentRange, childMaxExclusive); + } + + /// + /// Returns the exclusive maximum of the given value by subtracting one. + /// Supports hexadecimal and numeric values. If the value is neither, it returns the original string. + /// + /// The value to calculate the exclusive maximum for, either a hexadecimal or numeric string. + /// The exclusive maximum of the value as a string, or the original value if no subtraction can be performed. + private static string GetExclusiveMaxValue(string value) + { + // Check if the value is a valid hexadecimal string + if (ContainerCore.IsValidHex(value)) + { + try + { + long number = Convert.ToInt64(value, 16); + number -= 1; + return number.ToString("X"); // Return the result as a hexadecimal string + } + catch (OverflowException) + { + // Handle overflow in case of extremely large numbers + throw new ArgumentOutOfRangeException(nameof(value), "Value is too large to process as a hexadecimal."); + } + } + // Check if the value is a valid numeric string + else if (long.TryParse(value, out long numericValue)) + { + return (numericValue - 1).ToString(); // Subtract 1 and return as a string + } + else + { + // If the value is neither a hex nor a number, return it as is + return value; + } + } + + /// + /// Determines whether the provided string represents a valid hexadecimal number. + /// Supports both small and large hexadecimal numbers. + /// + /// The string to check for a valid hexadecimal format. + /// True if the string is a valid hexadecimal number; otherwise, false. + private static bool IsValidHex(string value) + { + // Try to parse the string as a hexadecimal number using BigInteger to handle larger values + return BigInteger.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 78066cf41c..eff82842dc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -224,7 +224,7 @@ public async Task GivenFeedRangeThrowsArgumentExceptionWhenChildFeedRangeHasInva await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, - expectedMessage: $"The provided string '' does not represent any known format."); + expectedMessage: $"The provided string, '', for 'childFeedRange', does not represent any known format."); } private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( @@ -315,7 +315,7 @@ public async Task GivenFeedRangeThrowsArgumentExceptionWhenParentFeedRangeHasInv await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, - expectedMessage: $"The provided string '' does not represent any known format."); + expectedMessage: $"The provided string, '', for 'parentFeedRange', does not represent any known format."); } private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) @@ -514,8 +514,8 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "10", "11", false, "10", "10", true, false }; // The child range, which starts at 10 and ends just before 11 (non-inclusive), does not fits entirely within the parent range, which starts and ends at 10 (inclusive), due to the parent's inclusive boundary at 10. - yield return new object[] { "A", "B", false, "A", "A", true, false }; // The child range, which starts at A and ends just before B (non-inclusive), does not fits entirely within the parent range, which starts and ends at A (inclusive), due to the parent's inclusive boundary at A. + yield return new object[] { "10", "11", false, "10", "10", true, true }; // The child range, which starts at 10 and ends just before 11 (non-inclusive), does not fits entirely within the parent range, which starts and ends at 10 (inclusive), due to the parent's inclusive boundary at 10. + yield return new object[] { "A", "B", false, "A", "A", true, true }; // The child range, which starts at A and ends just before B (non-inclusive), does not fits entirely within the parent range, which starts and ends at A (inclusive), due to the parent's inclusive boundary at A. } /// From 81e14997167e8a3db1019267b39ce68951ee6e0d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 30 Sep 2024 13:53:47 -0400 Subject: [PATCH 133/145] renaiming parent and child to x and y, respectively, for Container.cs --- .../src/Resource/Container/Container.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index c49b60ba1f..6046a37639 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1798,20 +1798,20 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// CosmosClient cosmosClient = cosmosClientBuilder.Build(); /// Database cosmosDatabase = cosmosClient.GetDatabase("your-database-id"); /// Container container = cosmosDatabase.GetContainer("your-container-id"); - /// FeedRange parentFeedRange = ...; // Define the parent feed range - /// FeedRange childFeedRange = ...; // Define the child feed range + /// FeedRange x = ...; // Define the parent feed range + /// FeedRange y = ...; // Define the child feed range /// /// bool isFeedRangePartOfAsync = await container.IsFeedRangePartOfAsync( - /// parentFeedRange, - /// childFeedRange, + /// x, + /// y, /// cancellationToken); /// ]]> /// /// /// True if the child feed range is a subset of the parent feed range; otherwise, false. public virtual Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, + Cosmos.FeedRange x, + Cosmos.FeedRange y, CancellationToken cancellationToken = default) { throw new NotImplementedException(); From 9dff5c51dc6e149e39d8b37d1289b2bd2f2cc43b Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 1 Oct 2024 16:46:34 -0400 Subject: [PATCH 134/145] test to include all types of containers. --- .../Resource/Container/ContainerCore.Items.cs | 138 ++-- .../IsFeedRangePartOfAsyncTests.cs | 713 ++++++++++++++---- .../Query/ConflictsE2ETest.cs | 2 +- .../EndToEndTraceWriterBaselineTests.cs | 8 +- 4 files changed, 649 insertions(+), 212 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 8775070325..9fc4d2af81 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1474,60 +1474,69 @@ internal static void EnsureConsistentInclusivity(List - /// Determines whether the specified child range is entirely within the bounds of the parent range. - /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. - /// - /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, - /// taking into account whether each boundary is inclusive or exclusive. - /// - /// - For the `Max` boundary: - /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. - /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. - /// - This applies to the following combinations: - /// - (false, false): Both max values are exclusive. - /// - (true, true): Both max values are inclusive. - /// - (true, false): Parent max is inclusive, child max is exclusive. - /// - /// - For the `Min` boundary: - /// - It simply checks whether the parent range contains the child range's min value. - /// - /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. - /// - /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: - /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: - /// - The parent range is exclusive at max, but the child range is inclusive. - /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: - /// - Both ranges are exclusive at max. - /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: - /// - Both ranges are inclusive at max. - /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: - /// - The parent range is inclusive at max, but the child range is exclusive. - /// - /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. - /// - /// Additionally, the method performs null checks on the parameters: - /// - If is null, an is thrown. - /// - If is null, an is thrown. - /// - /// The parent range to check against, representing the outer bounds. Cannot be null. - /// The child range being evaluated for subset inclusion within the parent range. Cannot be null. - /// - /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); - /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). - /// ]]> - /// - /// - /// Returns true if the child range is a subset of the parent range, meaning the child range's - /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// + /// Feature: Feed Range Subset Verification + /// + /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. + /// + /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, + /// taking into account whether each boundary is inclusive or exclusive. + /// + /// - For the `Max` boundary: + /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. + /// - If the parent range's max is inclusive and the child range's max is exclusive, this combination is not supported and a is thrown. + /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. + /// - This applies to the following combinations: + /// - (false, true): Parent max is exclusive, child max is inclusive. + /// - (true, true): Both max values are inclusive. + /// - (false, false): Both max values are exclusive. + /// - (true, false): Parent max is inclusive, child max is exclusive. + /// - **NotSupportedException Scenario:** This case is not supported because handling a scenario where the parent range has an inclusive maximum and the child range has an exclusive maximum requires additional logic that is not implemented. + /// - If encountered, a is thrown with a message explaining that this combination is not supported. + /// + /// - For the `Min` boundary: + /// - It checks whether the parent range contains the child range's min value, regardless of inclusivity. + /// + /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. + /// + /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: + /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: + /// - The parent range is exclusive at max, but the child range is inclusive. This is supported and will check if the parent contains the child's max. + /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: + /// - Both ranges are exclusive at max. This is supported and will check if the parent contains the child's max. + /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: + /// - Both ranges are inclusive at max. This is supported and will check if the max values are equal or if the parent contains the child's max. + /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: + /// - The parent range is inclusive at max, but the child range is exclusive. This combination is not supported and will result in a being thrown. + /// + /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. + /// + /// Additionally, the method performs null checks on the parameters: + /// - If is null, an is thrown. + /// - If is null, an is thrown. + /// + /// + /// Thrown when or is null. + /// + /// + /// Thrown when is inclusive at max and is exclusive at max. + /// This combination is not supported and requires specific handling. + /// + /// + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// ]]> + /// + /// + /// Returns true if the child range is a subset of the parent range, meaning the child range's + /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. /// - /// - /// Thrown when or is null. - /// internal static bool IsSubset( Documents.Routing.Range parentRange, Documents.Routing.Range childRange) @@ -1544,9 +1553,9 @@ internal static bool IsSubset( bool isMaxWithinParent = (parentRange.IsMaxInclusive, childRange.IsMaxInclusive) switch { - (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive - (true, false) => ContainerCore.IsChildMaxExclusiveWithinParent(parentRange, childRange), // (true, false): Parent max is inclusive, child max is exclusive - _ => ContainerCore.IsChildMaxWithinParent(parentRange, childRange.Max) // Default for the following combinations: + (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive + (true, false) => throw new NotSupportedException("The combination where the parent range's maximum is inclusive and the child range's maximum is exclusive is not supported in the current implementation. This case needs specific handling, which has not been implemented."), + _ => ContainerCore.IsChildMaxWithinParent(parentRange, childRange.Max) // Default for the following combinations: // (true, true): Both max values are inclusive // (false, false): Both max values are exclusive }; @@ -1569,23 +1578,6 @@ private static bool IsChildMaxWithinParent( return parentRange.Max == childRangeMax || parentRange.Contains(childRangeMax); } - /// - /// Determines whether the exclusive maximum of the child range is either equal to or contained within the parent range. - /// - /// The range representing the parent, which is compared against. - /// The range representing the child, whose exclusive maximum is checked. - /// True if the exclusive maximum of the child range is within or equal to the parent range's maximum; otherwise, false. - private static bool IsChildMaxExclusiveWithinParent( - Documents.Routing.Range parentRange, - Documents.Routing.Range childRange) - { - // Calculate the exclusive maximum of the child range - string childMaxExclusive = ContainerCore.GetExclusiveMaxValue(childRange.Max); - - // Check if the parent's max is equal to or contains the child's exclusive max - return ContainerCore.IsChildMaxWithinParent(parentRange, childMaxExclusive); - } - /// /// Returns the exclusive maximum of the given value by subtracting one. /// Supports hexadecimal and numeric values. If the value is neither, it returns the original string. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index eff82842dc..960ef7ebe0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -5,8 +5,10 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,20 +19,18 @@ public class IsFeedRangePartOfAsyncTests { private CosmosClient cosmosClient = null; private Cosmos.Database cosmosDatabase = null; - private ContainerInternal containerInternal = null; - private ContainerInternal hierarchicalContainerInternal = null; + public TestContext TestContext { get; set; } [TestInitialize] public async Task TestInit() { this.cosmosClient = TestCommon.CreateCosmosClient(); + this.cosmosDatabase = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); - string databaseName = Guid.NewGuid().ToString(); - DatabaseResponse cosmosDatabaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(databaseName); - this.cosmosDatabase = cosmosDatabaseResponse; - - this.containerInternal = await IsFeedRangePartOfAsyncTests.CreateSinglePartitionContainer(this.cosmosDatabase); - this.hierarchicalContainerInternal = await IsFeedRangePartOfAsyncTests.CreateHierachalContainer(this.cosmosDatabase); + await this.TestContext.SetContainerContextsAsync( + cosmosDatabase: this.cosmosDatabase, + createSinglePartitionContainerAsync: IsFeedRangePartOfAsyncTests.CreateSinglePartitionContainerAsync, + createHierarchicalPartitionContainerAsync: IsFeedRangePartOfAsyncTests.CreateHierarchicalPartitionContainerAsync); } [TestCleanup] @@ -49,24 +49,28 @@ public async Task TestCleanup() this.cosmosClient.Dispose(); } - private async static Task CreateSinglePartitionContainer(Database cosmosDatabase) + private async static Task CreateSinglePartitionContainerAsync(Database cosmosDatabase, PartitionKeyDefinitionVersion version) { ContainerResponse containerResponse = await cosmosDatabase.CreateContainerIfNotExistsAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk"); + new() + { + PartitionKeyDefinitionVersion = version, + Id = Guid.NewGuid().ToString(), + PartitionKeyPaths = new Collection { "/pk" } + }); return (ContainerInternal)containerResponse.Container; } - private async static Task CreateHierachalContainer(Database cosmosDatabase) + private async static Task CreateHierarchicalPartitionContainerAsync(Database cosmosDatabase, PartitionKeyDefinitionVersion version) { - ContainerProperties containerProperties = new ContainerProperties() - { - Id = Guid.NewGuid().ToString(), - PartitionKeyPaths = new Collection { "/pk", "/id" } - }; - - ContainerResponse containerResponse = await cosmosDatabase.CreateContainerIfNotExistsAsync(containerProperties); + ContainerResponse containerResponse = await cosmosDatabase.CreateContainerIfNotExistsAsync( + new() + { + PartitionKeyDefinitionVersion = version, + Id = Guid.NewGuid().ToString(), + PartitionKeyPaths = new Collection { "/pk", "/id" } + }); return (ContainerInternal)containerResponse.Container; } @@ -74,9 +78,9 @@ private async static Task CreateHierachalContainer(Database c /// /// CreateHierachalContainer(Database c [Owner("philipthomas-MSFT")] [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset")] [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] - [Description("Validate if the child partition key is part of the parent feed range.")] + [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset using V2 hash testContext")] + [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] + [Description("Validate if the child partition key is part of the parent feed range using either V1 or V2 PartitionKeyDefinitionVersion.")] public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( string parentMinimum, string parentMaximum, @@ -99,13 +105,45 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( { PartitionKey partitionKey = new("WA"); FeedRange feedRange = FeedRange.FromPartitionKey(partitionKey); - - bool actualIsFeedRangePartOfAsync = await this.containerInternal.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Where(context => !context.IsHierarchicalPartition) + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + if (actualIsFeedRangePartOfAsync != expectedIsFeedRangePartOfAsync) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.FeedRangeComparisonFailure, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + expectedIsFeedRangePartOfAsync, + actualIsFeedRangePartOfAsync))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -117,8 +155,8 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( /// (parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Where(context => context.IsHierarchicalPartition) + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None); + + if (actualIsFeedRangePartOfAsync != expectedIsFeedRangePartOfAsync) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.FeedRangeComparisonFailure, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + expectedIsFeedRangePartOfAsync, + actualIsFeedRangePartOfAsync))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -163,8 +232,8 @@ public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedR /// ( - async () => await this.containerInternal.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - childFeedRange: feedRange, - cancellationToken: CancellationToken.None)); - - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains(expectedMessage)); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + TExceeption exception = await Assert.ThrowsExceptionAsync( + async () => await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + childFeedRange: feedRange, + cancellationToken: CancellationToken.None)); + + if (exception == null) + { + lock (lockObject) + { + exceptions.Add(new Exception("Failed: {testContext}. Expected exception was null.")); + } + } + else if (!exception.Message.Contains(expectedMessage)) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.ExceptionMessageMismatch, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + expectedMessage, + exception.Message))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -254,8 +360,8 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange /// ', for 'parentFeedRange', does not represent any known format."); } - private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync(FeedRange feedRange, string expectedMessage) + private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + FeedRange feedRange, + string expectedMessage) where TException : Exception { try { - TException exception = await Assert.ThrowsExceptionAsync( - async () => await this.containerInternal.IsFeedRangePartOfAsync( - parentFeedRange: feedRange, - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), - cancellationToken: CancellationToken.None)); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + TException exception = await Assert.ThrowsExceptionAsync( + async () => await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: feedRange, + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + cancellationToken: CancellationToken.None)); + + if (exception == null) + { + lock (lockObject) + { + exceptions.Add(new Exception($"Failed: {containerContext}. Expected exception was null.")); + } + } + else if (!exception.Message.Contains(expectedMessage)) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.ExceptionMessageMismatch, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + expectedMessage, + exception.Message))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + } - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains(expectedMessage)); + /// + /// + /// + /// The starting value of the child feed range. + /// The ending value of the child feed range. + /// Specifies whether the maximum value of the child feed range is inclusive. + /// The starting value of the parent feed range. + /// The ending value of the parent feed range. + /// Specifies whether the maximum value of the parent feed range is inclusive. + [TestMethod] + [Owner("philipthomas-MSFT")] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeThrowsNotSupportedExceptionWhenChildIsMaxExclusiveAndParentIsMaxInclusive), DynamicDataSourceType.Method)] + public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseNotSupportedExceptionTestAsync( + string childMinimum, + string childMaximum, + bool childIsMaxInclusive, + string parentMinimum, + string parentMaximum, + bool parentIsMaxInclusive) + { + try + { + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + NotSupportedException exception = await Assert.ThrowsExceptionAsync( + async () => + await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + cancellationToken: CancellationToken.None)); + + if (exception == null) + { + lock (lockObject) + { + exceptions.Add(new Exception($"Failed: {containerContext}. Expected exception was null.")); + } + } + }); + + // Await all tasks to complete + await Task.WhenAll(tasks); + + // Handle the aggregated exceptions using the extension method + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -342,9 +559,9 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang /// @@ -356,18 +573,14 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang /// The ending value of the parent feed range. /// Specifies whether the maximum value of the parent feed range is inclusive. /// Indicates whether the child feed range is expected to be a subset of the parent feed range. - /// [TestMethod] [Owner("philipthomas-MSFT")] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] - [Description("Child feed range is or is not part of the parent feed range when both child's and parent's isMaxInclusive can be set to true or false.")] public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( string childMinimum, string childMaximum, @@ -379,12 +592,44 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi { try { - bool actualIsFeedRangePartOfAsync = await this.containerInternal.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), - cancellationToken: CancellationToken.None); - - Assert.AreEqual(expected: expectedIsFeedRangePartOfAsync, actual: actualIsFeedRangePartOfAsync); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + cancellationToken: CancellationToken.None); + + if (expectedIsFeedRangePartOfAsync != actualIsFeedRangePartOfAsync) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.FeedRangeComparisonFailure, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + expectedIsFeedRangePartOfAsync, + actualIsFeedRangePartOfAsync))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -396,7 +641,7 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi /// FeedRangeChildNotPartOfParentWhenBothIsMaxI yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. - } /// /// FeedRangeChildNotPartOfParentWhenChildAndPa /// FeedRangeChildPartOfParentWhenChildIsMaxInc /// FeedRangeChildNotPartOfParentWhenChildIsMax /// FeedRangeChildNotPartOfParentWhenChildIsMax /// private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "10", "11", false, "10", "10", true, true }; // The child range, which starts at 10 and ends just before 11 (non-inclusive), does not fits entirely within the parent range, which starts and ends at 10 (inclusive), due to the parent's inclusive boundary at 10. - yield return new object[] { "A", "B", false, "A", "A", true, true }; // The child range, which starts at A and ends just before B (non-inclusive), does not fits entirely within the parent range, which starts and ends at A (inclusive), due to the parent's inclusive boundary at A. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true }; // + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true}; // + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // + yield return new object[] { "10", "11", false, "10", "10", true }; // + yield return new object[] { "A", "B", false, "A", "A", true }; // } /// /// /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + private static IEnumerable FeedRangeThrowsNotSupportedExceptionWhenChildIsMaxExclusiveAndParentIsMaxInclusive() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 3333333333333333, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to just before 6666666666666666, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to just before FFFFFFFFFFFFFFFF, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending just before 7333333333333333, does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3333333333333333' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7333333333333333' to 'FFFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to '3FFFFFFFFFFFFFFF') + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3FFFFFFFFFFFFFFF' to '4CCCCCCCCCCCCCCC' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '4CCCCCCCCCCCCCCC' to '5999999999999999' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '5999999999999999' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '6666666666666666' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7333333333333333' to '7FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "10", "11", false, "10", "10", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '10' to '11' vs '10' to '10') + yield return new object[] { "A", "B", false, "A", "A", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: 'A' to 'B' vs 'A' to 'A') } /// /// FeedRangeChildPartOfParentWhenBothChildAndP /// FeedRangeChildNotPartOfParentWhenBothChildA /// /// + /// The version of the PartitionKeyDefinition (V1 or V2) used for the validation. [TestMethod] [Owner("philipthomas-MSFT")] public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithParentIsMinInclusiveFalse() @@ -611,8 +868,8 @@ await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( /// ( - async () => await this.containerInternal - .IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(parentFeedRange), - childFeedRange: new FeedRangeEpk(childFeedRange), - cancellationToken: CancellationToken.None)); - - Assert.IsNotNull(exception); - Assert.IsTrue(exception.Message.Contains("IsMinInclusive must be true.")); + if (!this.TestContext.TryGetContainerContexts(out List containerContexts)) + { + this.TestContext.WriteLine("ContainerContexts do not exist in TestContext.Properties."); + } + + ConcurrentBag exceptions = new(); + object lockObject = new(); + + IEnumerable tasks = containerContexts + .Select(async containerContext => + { + this.TestContext.LogTestExecutionForContainer(containerContext); + + ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( + async () => await containerContext.Container + .IsFeedRangePartOfAsync( + parentFeedRange: new FeedRangeEpk(parentFeedRange), + childFeedRange: new FeedRangeEpk(childFeedRange), + cancellationToken: CancellationToken.None)); + + if (exception == null) + { + lock (lockObject) + { + exceptions.Add(new Exception($"Failed: {containerContext}. Expected exception was null.")); + } + } + else if (!exception.Message.Contains("IsMinInclusive must be true.")) + { + lock (lockObject) + { + exceptions.Add( + new Exception( + string.Format( + TestContextExtensions.IsMinInclusiveExceptionMismatch, + containerContext.Container.Id, + containerContext.Version, + containerContext.IsHierarchicalPartition ? "Hierarchical Partitioning" : "Single Partitioning", + exception.Message))); + } + } + }); + + await Task.WhenAll(tasks); + + this.TestContext.HandleAggregatedExceptions(exceptions); } catch (Exception exception) { @@ -663,15 +957,21 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// Then the result should be indicating if the child is a subset of the parent /// ]]> /// + /// Indicates whether the parent range's minimum value is inclusive. + /// Indicates whether the parent range's maximum value is inclusive. + /// The minimum value of the parent range. + /// The maximum value of the parent range. + /// Indicates whether the child range's minimum value is inclusive. + /// Indicates whether the child range's maximum value is inclusive. + /// The minimum value of the child range. + /// The maximum value of the child range. + /// A boolean indicating whether the child feed range is expected to be a subset of the parent feed range. True if the child is a subset, false otherwise. [TestMethod] [Owner("philipthomas-MSFT")] [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both parent and child ranges (A to Z) are fully inclusive and equal, child is a subset")] [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both parent and child ranges (A to A) are fully inclusive and equal, and min and max range is the same, child is a subset")] [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive but min and max ranges are not the same (A to A, B to B), child is not a subset")] [DataRow(true, true, "B", "B", true, true, "A", "A", false, DisplayName = "(true, true) Given parent range (B to B) is fully inclusive and child range (A to A) is fully inclusive, child is not a subset")] - [DataRow(true, true, "A", "Z", true, false, "A", "Y", true, DisplayName = "(true, false) Given parent range (A to Z) is fully inclusive and child range (A to Y) has an exclusive max, child is a subset")] - [DataRow(true, true, "A", "Y", true, false, "A", "Z", false, DisplayName = "(true, false) Given parent range (A to Y) is fully inclusive and child range (A to Z) has an exclusive max, child is not a subset")] - [DataRow(true, true, "A", "Z", true, false, "A", "Z", true, DisplayName = "(true, false) Given parent range (A to Z) is fully inclusive and child range (A to Z) has an exclusive max, child is a subset")] [DataRow(true, false, "A", "Z", true, true, "A", "Y", true, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Y) is fully inclusive, child is a subset")] [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Y) has an exclusive max but child range (A to Z) exceeds the parent’s max with an inclusive bound, child is not a subset")] [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Z) is fully inclusive, child is not a subset")] @@ -698,16 +998,57 @@ public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( actual: actualIsSubset); } + /// + /// + /// + /// Indicates whether the parent range's minimum value is inclusive. + /// Indicates whether the parent range's maximum value is inclusive. + /// The minimum value of the parent range. + /// The maximum value of the parent range. + /// Indicates whether the child range's minimum value is inclusive. + /// Indicates whether the child range's maximum value is inclusive. + /// The minimum value of the child range. + /// The maximum value of the child range. + [TestMethod] + [Owner("philipthomas-MSFT")] + [DataRow(true, true, "A", "Y", true, false, "A", "W", DisplayName = "(true, false) Given parent range (A to Y) is inclusive at min and max, and child range (A to W) is inclusive at min and exclusive at max, expects NotSupportedException")] + [DataRow(true, true, "A", "Z", true, false, "A", "X", DisplayName = "(true, false) Given parent range (A to Z) is inclusive at min and max, and child range (A to X) is inclusive at min and exclusive at max, expects NotSupportedException")] + [DataRow(true, true, "A", "Y", true, false, "A", "Y", DisplayName = "(true, false) Given parent range (A to Y) is inclusive at min and max, and child range (A to Y) is inclusive at min and exclusive at max, expects NotSupportedException")] + public void GivenParentMaxInclusiveChildMaxExclusiveWhenCallingIsSubsetThenExpectNotSupportedExceptionIsThrown( + bool parentIsMinInclusive, + bool parentIsMaxInclusive, + string parentMinValue, + string parentMaxValue, + bool childIsMinInclusive, + bool childIsMaxInclusive, + string childMinValue, + string childMaxValue) + { + NotSupportedException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( + parentRange: new Documents.Routing.Range(min: parentMinValue, max: parentMaxValue, isMinInclusive: parentIsMinInclusive, isMaxInclusive: parentIsMaxInclusive), + childRange: new Documents.Routing.Range(min: childMinValue, max: childMaxValue, isMinInclusive: childIsMinInclusive, isMaxInclusive: childIsMaxInclusive))); + + Assert.IsNotNull(exception); + } + /// /// + /// [TestMethod] [Owner("philipthomas-MSFT")] public void GivenNullParentFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() @@ -723,7 +1064,7 @@ public void GivenNullParentFeedRangeWhenCallingIsSubsetThenArgumentNullException /// + /// Attempts to retrieve the list of objects stored in the properties. + /// If the property "ContainerContexts" exists and is of type , it is returned via the out parameter. + /// Returns true if successful, false otherwise. + /// + /// The instance from which to attempt retrieving the container contexts. + /// When this method returns, contains the if the retrieval was successful; otherwise, null. + /// true if the retrieval was successful; otherwise, false. + public static bool TryGetContainerContexts(this TestContext testContext, out List containerContexts) + { + if (testContext.Properties["ContainerContexts"] is List contexts) + { + containerContexts = contexts; + return true; + } + else + { + containerContexts = null; + return false; + } + } + + /// + /// Logs exceptions, prints details, and throws an AssertFailedException with the aggregated exceptions. + /// + /// The instance from which to attempt retrieving the container contexts. + /// A collection of exceptions to aggregate and log. + public static void HandleAggregatedExceptions(this TestContext testContext, ConcurrentBag exceptions) + { + // Check if any exceptions were captured + if (exceptions.Any()) + { + // Aggregate the exceptions + AggregateException aggregateException = new AggregateException(exceptions); + + // Log out the details of each inner exception + foreach (Exception innerException in aggregateException.InnerExceptions) + { + testContext.WriteLine($"Exception: {innerException.Message}"); + testContext.WriteLine(innerException.StackTrace); + } + + // Throw an AssertFailedException with the aggregated exceptions + throw new AssertFailedException("One or more assertions failed. See inner exceptions for details.", aggregateException); + } + } + + // + /// Asynchronously sets up the ContainerContexts property in the TestContext by creating containers with specified partition key definitions. + /// + /// The instance from which to attempt retrieving the container contexts. + /// The Cosmos database used for creating the containers. + /// A delegate function that creates a container with a single partition key definition version asynchronously. + /// A delegate function that creates a container with a hierarchical partition key definition version asynchronously. + /// A task representing the asynchronous operation, which sets up the ContainerContexts property in the TestContext. + public static async Task SetContainerContextsAsync( + this TestContext testContext, + Database cosmosDatabase, + Func> createSinglePartitionContainerAsync, + Func> createHierarchicalPartitionContainerAsync) + { + testContext.Properties["ContainerContexts"] = new List() + { + new (await createSinglePartitionContainerAsync(cosmosDatabase, PartitionKeyDefinitionVersion.V1), PartitionKeyDefinitionVersion.V1, false), + new (await createSinglePartitionContainerAsync(cosmosDatabase, PartitionKeyDefinitionVersion.V2), PartitionKeyDefinitionVersion.V2, false), + new (await createHierarchicalPartitionContainerAsync(cosmosDatabase, PartitionKeyDefinitionVersion.V1), PartitionKeyDefinitionVersion.V1, true), + new (await createHierarchicalPartitionContainerAsync(cosmosDatabase, PartitionKeyDefinitionVersion.V2), PartitionKeyDefinitionVersion.V2, true), + }; + } + + /// + /// Logs a message indicating the current container test being executed. + /// + /// The instance from which to attempt retrieving the container contexts. + /// The container context that is being executed. + public static void LogTestExecutionForContainer(this TestContext testContext, ContainerContext containerContext) + { + string partitionType = containerContext.IsHierarchicalPartition ? "Hierarchical Partition" : "Single Partition"; + + testContext.WriteLine($"Executing test for container with ID: '{containerContext.Container.Id}', " + + $"Partition Key Definition Version: '{containerContext.Version}', " + + $"{partitionType}."); + } + } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs index 0edd95b22d..04a062099e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs @@ -135,7 +135,7 @@ private async Task InsertWithConflict(IReadOnlyList<(CosmosClient Client, Contai /// /// Inserts items from multiple clients. /// - /// Containers to insert documents to. + /// ContainerContext to insert documents to. /// Format of the document with placeholders for insertion iteration (one round across all clients) and optional client index. /// Optional filter that determines whether a client should be used for insertion. /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 53689de374..94ac851147 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -814,12 +814,12 @@ public async Task TypedPointOperationsAsync() //---------------------------------------------------------------- //{ // startLineNumber = GetLineNumber(); - // ContainerInternal containerInternal = (ContainerInternal)container; + // ContainerInternal V1Container = (ContainerInternal)container; // List patchOperations = new List() // { // PatchOperation.Replace("/someField", "42") // }; - // ItemResponse patchResponse = await containerInternal.PatchItemAsync( + // ItemResponse patchResponse = await V1Container.PatchItemAsync( // id: "9001", // partitionKey: new PartitionKey("9001"), // patchOperations: patchOperations); @@ -936,12 +936,12 @@ public async Task StreamPointOperationsAsync() //{ // startLineNumber = GetLineNumber(); // ItemRequestOptions requestOptions = new ItemRequestOptions(); - // ContainerInternal containerInternal = (ContainerInternal)container; + // ContainerInternal V1Container = (ContainerInternal)container; // List patch = new List() // { // PatchOperation.Replace("/someField", "42") // }; - // ResponseMessage patchResponse = await containerInternal.PatchItemStreamAsync( + // ResponseMessage patchResponse = await V1Container.PatchItemStreamAsync( // id: "9001", // partitionKey: new PartitionKey("9001"), // patchOperations: patch, From fe03634cc17cd21b6e3d04ddcb238ef8f67c95d8 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 2 Oct 2024 08:38:21 -0400 Subject: [PATCH 135/145] remove dead code. --- .../Resource/Container/ContainerCore.Items.cs | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 9fc4d2af81..712e3153ac 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1577,52 +1577,5 @@ private static bool IsChildMaxWithinParent( { return parentRange.Max == childRangeMax || parentRange.Contains(childRangeMax); } - - /// - /// Returns the exclusive maximum of the given value by subtracting one. - /// Supports hexadecimal and numeric values. If the value is neither, it returns the original string. - /// - /// The value to calculate the exclusive maximum for, either a hexadecimal or numeric string. - /// The exclusive maximum of the value as a string, or the original value if no subtraction can be performed. - private static string GetExclusiveMaxValue(string value) - { - // Check if the value is a valid hexadecimal string - if (ContainerCore.IsValidHex(value)) - { - try - { - long number = Convert.ToInt64(value, 16); - number -= 1; - return number.ToString("X"); // Return the result as a hexadecimal string - } - catch (OverflowException) - { - // Handle overflow in case of extremely large numbers - throw new ArgumentOutOfRangeException(nameof(value), "Value is too large to process as a hexadecimal."); - } - } - // Check if the value is a valid numeric string - else if (long.TryParse(value, out long numericValue)) - { - return (numericValue - 1).ToString(); // Subtract 1 and return as a string - } - else - { - // If the value is neither a hex nor a number, return it as is - return value; - } - } - - /// - /// Determines whether the provided string represents a valid hexadecimal number. - /// Supports both small and large hexadecimal numbers. - /// - /// The string to check for a valid hexadecimal format. - /// True if the string is a valid hexadecimal number; otherwise, false. - private static bool IsValidHex(string value) - { - // Try to parse the string as a hexadecimal number using BigInteger to handle larger values - return BigInteger.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _); - } } } From 09fa5d06a3c8b762236b7462832d6449a0a0efce Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 2 Oct 2024 09:05:56 -0400 Subject: [PATCH 136/145] fix params --- Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 6046a37639..81b2335653 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1787,8 +1787,8 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// Determines whether the given child feed range is a part of the specified parent feed range. /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. /// - /// The feed range representing the parent range. - /// The feed range representing the child range. + /// The feed range representing the parent range. + /// The feed range representing the child range. /// A token to cancel the operation if needed. /// /// From 248b9299fa8740831da1c01c666302e2cfe09bad Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 2 Oct 2024 09:07:23 -0400 Subject: [PATCH 137/145] reverted these --- .../Query/ConflictsE2ETest.cs | 2 +- .../Tracing/EndToEndTraceWriterBaselineTests.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs index 04a062099e..0edd95b22d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/ConflictsE2ETest.cs @@ -135,7 +135,7 @@ private async Task InsertWithConflict(IReadOnlyList<(CosmosClient Client, Contai /// /// Inserts items from multiple clients. /// - /// ContainerContext to insert documents to. + /// Containers to insert documents to. /// Format of the document with placeholders for insertion iteration (one round across all clients) and optional client index. /// Optional filter that determines whether a client should be used for insertion. /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 94ac851147..53689de374 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -814,12 +814,12 @@ public async Task TypedPointOperationsAsync() //---------------------------------------------------------------- //{ // startLineNumber = GetLineNumber(); - // ContainerInternal V1Container = (ContainerInternal)container; + // ContainerInternal containerInternal = (ContainerInternal)container; // List patchOperations = new List() // { // PatchOperation.Replace("/someField", "42") // }; - // ItemResponse patchResponse = await V1Container.PatchItemAsync( + // ItemResponse patchResponse = await containerInternal.PatchItemAsync( // id: "9001", // partitionKey: new PartitionKey("9001"), // patchOperations: patchOperations); @@ -936,12 +936,12 @@ public async Task StreamPointOperationsAsync() //{ // startLineNumber = GetLineNumber(); // ItemRequestOptions requestOptions = new ItemRequestOptions(); - // ContainerInternal V1Container = (ContainerInternal)container; + // ContainerInternal containerInternal = (ContainerInternal)container; // List patch = new List() // { // PatchOperation.Replace("/someField", "42") // }; - // ResponseMessage patchResponse = await V1Container.PatchItemStreamAsync( + // ResponseMessage patchResponse = await containerInternal.PatchItemStreamAsync( // id: "9001", // partitionKey: new PartitionKey("9001"), // patchOperations: patch, From ccc3009238ab0448c8db532f80beb221e279ad16 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 2 Oct 2024 09:47:41 -0400 Subject: [PATCH 138/145] remove named parameters because local test use parent/child parameters, but CI pipeline run uses x/y. --- .../IsFeedRangePartOfAsyncTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 960ef7ebe0..8519e7454c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -120,8 +120,8 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, + new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + feedRange, cancellationToken: CancellationToken.None); if (actualIsFeedRangePartOfAsync != expectedIsFeedRangePartOfAsync) @@ -197,8 +197,8 @@ public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedR this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), - childFeedRange: feedRange, + new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + feedRange, cancellationToken: CancellationToken.None); if (actualIsFeedRangePartOfAsync != expectedIsFeedRangePartOfAsync) @@ -318,8 +318,8 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange TExceeption exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), - childFeedRange: feedRange, + new FeedRangeEpk(new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)), + feedRange, cancellationToken: CancellationToken.None)); if (exception == null) @@ -446,8 +446,8 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang TException exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: feedRange, - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), + feedRange, + new FeedRangeEpk(new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false)), cancellationToken: CancellationToken.None)); if (exception == null) @@ -530,8 +530,8 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi NotSupportedException exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), cancellationToken: CancellationToken.None)); if (exception == null) @@ -606,8 +606,8 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - childFeedRange: new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), cancellationToken: CancellationToken.None); if (expectedIsFeedRangePartOfAsync != actualIsFeedRangePartOfAsync) @@ -906,8 +906,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container .IsFeedRangePartOfAsync( - parentFeedRange: new FeedRangeEpk(parentFeedRange), - childFeedRange: new FeedRangeEpk(childFeedRange), + new FeedRangeEpk(parentFeedRange), + new FeedRangeEpk(childFeedRange), cancellationToken: CancellationToken.None)); if (exception == null) From 77dab0622c9e9ec948e9006b2d680ed3815846d7 Mon Sep 17 00:00:00 2001 From: Philip Thomas <86612891+philipthomas-MSFT@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:19:44 -0400 Subject: [PATCH 139/145] Update Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs Co-authored-by: Kiran Kumar Kolli --- .../src/Resource/Container/Container.cs | 3611 +++++++++-------- 1 file changed, 1806 insertions(+), 1805 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 81b2335653..28689da0fd 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1,1814 +1,1815 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - - /// - /// Operations for reading, replacing, or deleting a specific, existing container or item in a container by id. - /// There are two different types of operations. - /// 1. The object operations where it serializes and deserializes the item on request/response - /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream - /// See for creating new containers, and reading/querying all containers. - /// - /// - /// Note: all these operations make calls against a fixed budget. - /// You should design your system such that these calls scale sub linearly with your application. - /// For instance, do not call `container.readAsync()` before every single `container.readItemAsync()` call to ensure the container exists; - /// do this once on application start up. - /// - public abstract class Container - { - /// - /// The Id of the Cosmos container - /// - public abstract string Id { get; } - - /// - /// Returns the parent Database reference - /// - public abstract Database Database { get; } - - /// - /// Returns the conflicts - /// - public abstract Conflicts Conflicts { get; } - - /// - /// Returns the scripts - /// - public abstract Scripts.Scripts Scripts { get; } - - /// - /// Reads a from the Azure Cosmos service as an asynchronous operation. - /// - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the read resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// - /// - /// - public abstract Task ReadContainerAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Reads a from the Azure Cosmos service as an asynchronous operation. - /// - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// - /// A containing a containing the read resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// - /// - /// - public abstract Task ReadContainerStreamAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Replace a from the Azure Cosmos service as an asynchronous operation. - /// - /// The object. - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the replace resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// Update the container to disable automatic indexing - /// - /// - /// - /// - public abstract Task ReplaceContainerAsync( - ContainerProperties containerProperties, - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Replace a from the Azure Cosmos service as an asynchronous operation. - /// - /// The . - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// - /// A containing a containing the replace resource record. - /// - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// - /// - public abstract Task ReplaceContainerStreamAsync( - ContainerProperties containerProperties, - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Delete a from the Azure Cosmos DB service as an asynchronous operation. - /// - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// A containing a which will contain information about the request issued. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// - /// - /// - public abstract Task DeleteContainerAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Delete a from the Azure Cosmos DB service as an asynchronous operation. - /// - /// (Optional) The options for the container request. - /// (Optional) representing request cancellation. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// - /// - /// - /// A containing a which will contain information about the request issued. - public abstract Task DeleteContainerStreamAsync( - ContainerRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Gets container throughput in measurement of request units per second in the Azure Cosmos service. - /// - /// (Optional) representing request cancellation. - /// Provisioned throughput in request units per second - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// The provisioned throughput for this container. - /// - /// - /// - /// Null value indicates a container with no throughput provisioned. - /// - /// - /// - /// The following example shows how to get the throughput. - /// - /// - /// - /// - /// Request Units - /// Set throughput on a container - public abstract Task ReadThroughputAsync( - CancellationToken cancellationToken = default); - - /// - /// Gets container throughput in measurement of request units per second in the Azure Cosmos service. - /// - /// The options for the throughput request. - /// (Optional) representing request cancellation. - /// The throughput response - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// The provisioned throughput for this container. - /// - /// - /// The following example shows how to get the throughput - /// - /// - /// - /// - /// - /// The following example shows how to get throughput, MinThroughput and is replace in progress - /// - /// - /// - /// - /// Request Units - /// Set throughput on a container - public abstract Task ReadThroughputAsync( - RequestOptions requestOptions, - CancellationToken cancellationToken = default); - - /// - /// Sets throughput provisioned for a container in measurement of request units per second in the Azure Cosmos service. - /// - /// The Cosmos container throughput, expressed in Request Units per second. - /// (Optional) The options for the throughput request. - /// (Optional) representing request cancellation. - /// The throughput response. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// The provisioned throughput for this container. - /// - /// - /// The following example shows how to get the throughput. - /// - /// - /// - /// - /// Request Units - /// Set throughput on a container - public abstract Task ReplaceThroughputAsync( - int throughput, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Sets throughput provisioned for a container in measurement of request units per second in the Azure Cosmos service. - /// - /// The Cosmos container throughput expressed in Request Units per second. - /// (Optional) The options for the throughput request. - /// (Optional) representing request cancellation. - /// The throughput response. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// The following example shows how to replace the fixed throughput. - /// - /// - /// - /// - /// - /// The following example shows how to replace the autoscale provisioned throughput - /// - /// - /// - /// - /// - /// Request Units - /// Set throughput on a container - /// - public abstract Task ReplaceThroughputAsync( - ThroughputProperties throughputProperties, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Creates a Item as an asynchronous operation in the Azure Cosmos service. - /// - /// A containing the payload. - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// The that was created contained within a object representing the service response for the asynchronous operation. - /// - /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// This example creates an item in a Cosmos container. - /// - /// - /// - /// - public abstract Task CreateItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Creates a item as an asynchronous operation in the Azure Cosmos service. - /// - /// A JSON serializable object that must contain an id property. to implement a custom serializer - /// for the item. If not specified will be populated by extracting from {T} - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// The that was created contained within a object representing the service response for the asynchronous operation. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// (test, new PartitionKey(test.status)); - /// ]]> - /// - /// - public abstract Task> CreateItemAsync( - T item, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Reads a item from the Azure Cosmos service as an asynchronous operation. - /// - /// The Cosmos item id - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the read resource record. - /// - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. - /// - /// - /// Read a response as a stream. - /// - /// - /// - /// - public abstract Task ReadItemStreamAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Reads a item from the Azure Cosmos service as an asynchronous operation. - /// - /// The Cosmos item id - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps the read resource record. - /// - /// - /// Items contain meta data that can be obtained by mapping these meta data attributes to properties in . - /// * "_ts": Gets the last modified time stamp associated with the item from the Azure Cosmos DB service. - /// * "_etag": Gets the entity tag associated with the item from the Azure Cosmos DB service. - /// * "ttl": Gets the time to live in seconds of the item in the Azure Cosmos DB service. - /// Note that, this API does not support the usage of property at the moment. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); - /// - /// Example 2: Reading Item with Implicit Casting - /// - /// This example shows how to read an item from the container and implicitly cast the - /// response directly to a `ToDoActivity` object, omitting the metadata in the `ItemResponse`. - /// - /// ToDoActivity toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); - /// - /// ]]> - /// - /// - public abstract Task> ReadItemAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Upserts an item stream as an asynchronous operation in the Azure Cosmos service. - /// - /// A containing the payload. - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the read resource record. - /// - /// - /// The Stream operation only throws on client side exceptions. - /// This is to increase performance and prevent the overhead of throwing exceptions. - /// Check the HTTP status code on the response to check if the operation failed. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// - /// Upsert result i.e. creation or replace can be identified by the status code: - /// 201 - item created - /// 200 - item replaced - /// - /// - /// - /// Upsert a Stream containing the item to Cosmos - /// - /// - /// - /// - public abstract Task UpsertItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Upserts an item as an asynchronous operation in the Azure Cosmos service. - /// - /// A JSON serializable object that must contain an id property. to implement a custom serializer - /// for the item. If not specified will be populated by extracting from {T} - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// The that was upserted contained within a object representing the service response for the asynchronous operation. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// Upsert result i.e. creation or replace can be identified by the status code: - /// 201 - item created - /// 200 - item replaced - /// - /// - /// - /// - /// item = await this.container.UpsertItemAsync(test, new PartitionKey(test.status)); - /// ]]> - /// - /// - public abstract Task> UpsertItemAsync( - T item, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Replaces a item in the Azure Cosmos service as an asynchronous operation. - /// - /// - /// The item's partition key value is immutable. - /// To change an item's partition key value you must delete the original item and insert a new item. - /// - /// A containing the payload. - /// The Cosmos item id - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the replace resource record. - /// - /// - /// The Stream operation only throws on client side exceptions. - /// This is to increase performance and prevent the overhead of throwing exceptions. - /// Check the HTTP status code on the response to check if the operation failed. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// Replace an item in Cosmos - /// - /// - /// - /// - public abstract Task ReplaceItemStreamAsync( - Stream streamPayload, - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Replaces a item in the Azure Cosmos service as an asynchronous operation. - /// - /// - /// The item's partition key value is immutable. - /// To change an item's partition key value you must delete the original item and insert a new item. - /// - /// A JSON serializable object that must contain an id property. to implement a custom serializer. - /// The Cosmos item id of the existing item. - /// for the item. If not specified will be populated by extracting from {T} - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps the updated resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// (test, test.id, new PartitionKey(test.status)); - /// ]]> - /// - /// - public abstract Task> ReplaceItemAsync( - T item, - string id, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Reads multiple items from a container using Id and PartitionKey values. - /// - /// List of item.Id and - /// Request Options for ReadMany Operation - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the response. - /// - /// is meant to perform better latency-wise than a query with IN statements to fetch a large number of independent items. - /// - /// - /// itemList = new List<(string, PartitionKey)> - /// { - /// ("Id1", new PartitionKey("pkValue1")), - /// ("Id2", new PartitionKey("pkValue2")), - /// ("Id3", new PartitionKey("pkValue3")) - /// }; - /// - /// using (ResponseMessage response = await this.Container.ReadManyItemsStreamAsync(itemList)) - /// { - /// if (!response.IsSuccessStatusCode) - /// { - /// //Handle and log exception - /// return; - /// } - /// - /// //Read or do other operations with the stream - /// using (StreamReader streamReader = new StreamReader(response.Content)) - /// { - /// string content = streamReader.ReadToEndAsync(); - /// } - /// - /// } - /// ]]> - /// - /// - public abstract Task ReadManyItemsStreamAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Reads multiple items from a container using Id and PartitionKey values. - /// - /// List of item.Id and - /// Request Options for ReadMany Operation - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps the typed items. - /// - /// is meant to perform better latency-wise than a query with IN statements to fetch a large number of independent items. - /// - /// - /// itemList = new List<(string, PartitionKey)> - /// { - /// ("Id1", new PartitionKey("pkValue1")), - /// ("Id2", new PartitionKey("pkValue2")), - /// ("Id3", new PartitionKey("pkValue3")) - /// }; - /// - /// FeedResponse feedResponse = this.Container.ReadManyItemsAsync(itemList); - /// ]]> - /// - /// - public abstract Task> ReadManyItemsAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Patches an item in the Azure Cosmos service as an asynchronous operation. - /// - /// - /// The item's partition key value is immutable. - /// To change an item's partition key value you must delete the original item and insert a new item. - /// The patch operations are atomic and are executed sequentially. - /// By default, resource body will be returned as part of the response. User can request no content by setting flag to false. - /// Note that, this API does not support the usage of property at the moment. - /// - /// The Cosmos item id of the item to be patched. - /// for the item - /// Represents a list of operations to be sequentially applied to the referred Cosmos item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps the updated resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); - /// - /// Example 2: Reading Item with Implicit Casting - /// - /// This example shows how to read an item from the container and implicitly cast the - /// response directly to a `ToDoActivity` object, omitting the metadata in the `ItemResponse`. - /// - /// ToDoActivity toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); - /// - /// /* toDoActivity = { - /// "id" : "someId", - /// "status" : "someStatusPK", - /// "description" : "someDescription", - /// "frequency" : 7 - /// }*/ - /// - /// List patchOperations = new List() - /// { - /// PatchOperation.Add("/daysOfWeek", new string[]{"Monday", "Thursday"}), - /// PatchOperation.Replace("/frequency", 2), - /// PatchOperation.Remove("/description") - /// }; - /// - /// ItemResponse item = await this.container.PatchItemAsync(toDoActivity.id, new PartitionKey(toDoActivity.status), patchOperations); - /// /* item.Resource = { - /// "id" : "someId", - /// "status" : "someStatusPK", - /// "description" : null, - /// "frequency" : 2, - /// "daysOfWeek" : ["Monday", "Thursday"] - /// }*/ - /// ]]> - /// - /// - /// Supported partial document update modes - public abstract Task> PatchItemAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Patches an item in the Azure Cosmos service as an asynchronous operation. - /// - /// - /// The item's partition key value is immutable. - /// To change an item's partition key value you must delete the original item and insert a new item. - /// The patch operations are atomic and are executed sequentially. - /// By default, resource body will be returned as part of the response. User can request no content by setting flag to false. - /// - /// The Cosmos item id - /// The partition key for the item. - /// Represents a list of operations to be sequentially applied to the referred Cosmos item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the patched resource record. - /// - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// This is to increase performance and prevent the overhead of throwing exceptions. - /// Check the HTTP status code on the response to check if the operation failed. - /// - /// - /// - /// - /// Supported partial document update modes - public abstract Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Delete a item from the Azure Cosmos service as an asynchronous operation. - /// - /// The Cosmos item id - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// - /// A containing a which wraps a containing the delete resource record. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// For delete operations, the will be null. Item content is not expected in the response. - /// - /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. - /// - /// - /// Delete an item from Cosmos - /// - /// - /// - /// - public abstract Task DeleteItemStreamAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Delete a item from the Azure Cosmos service as an asynchronous operation. - /// - /// The Cosmos item id - /// The partition key for the item. - /// (Optional) The options for the item request. - /// (Optional) representing request cancellation. - /// A containing a which will contain information about the request issued. - /// - /// is always null - /// - /// For delete operations, the will be null. Item content is not expected in the response. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// ("id", new PartitionKey("partitionKey")); - /// ]]> - /// - /// - public abstract Task> DeleteItemAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. - /// For more information on preparing SQL statements with parameterized values, please see . - /// - /// The Cosmos SQL query definition. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition - /// - /// @expensive") - /// .WithParameter("@expensive", 9000); - /// using (FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - /// queryDefinition, - /// null, - /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) - /// { - /// using (StreamReader sr = new StreamReader(response.Content)) - /// using (JsonTextReader jtr = new JsonTextReader(sr)) - /// { - /// JObject result = JObject.Load(jtr); - /// } - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetItemQueryStreamIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. - /// For more information on preparing SQL statements with parameterized values, please see . - /// - /// The Cosmos SQL query definition. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// Create a query to get all the ToDoActivity that have a cost greater than 9000 - /// - /// @expensive") - /// .WithParameter("@expensive", 9000); - /// using (FeedIterator feedIterator = this.Container.GetItemQueryIterator( - /// queryDefinition, - /// null, - /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// foreach(var item in await feedIterator.ReadNextAsync()) - /// { - /// Console.WriteLine(item.cost); - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetItemQueryIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement. It returns a FeedIterator. - /// - /// The Cosmos SQL query text. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// 1. Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition - /// - /// 9000", - /// null, - /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) - /// { - /// using (StreamReader sr = new StreamReader(response.Content)) - /// using (JsonTextReader jtr = new JsonTextReader(sr)) - /// { - /// JObject result = JObject.Load(jtr); - /// } - /// } - /// } - /// } - /// ]]> - /// - /// - /// - /// 2. Creates a FeedIterator to get all the ToDoActivity. - /// - /// - /// - /// - public abstract FeedIterator GetItemQueryStreamIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement. It returns a FeedIterator. - /// - /// The Cosmos SQL query text. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// 1. Create a query to get all the ToDoActivity that have a cost greater than 9000 - /// - /// feedIterator = this.Container.GetItemQueryIterator( - /// "select * from ToDos t where t.cost > 9000", - /// null, - /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// foreach(var item in await feedIterator.ReadNextAsync()) - /// { - /// Console.WriteLine(item.cost); - /// } - /// } - /// } - /// ]]> - /// - /// - /// - /// 2. Create a FeedIterator to get all the ToDoActivity. - /// - /// feedIterator = this.Container.GetItemQueryIterator( - /// null, - /// null, - /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// foreach(var item in await feedIterator.ReadNextAsync()) - /// { - /// Console.WriteLine(item.cost); - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetItemQueryIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. - /// For more information on preparing SQL statements with parameterized values, please see . - /// - /// A FeedRange obtained from - /// The Cosmos SQL query definition. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api - /// - /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition - /// - /// feedRanges = await this.Container.GetFeedRangesAsync(); - /// // Distribute feedRanges across multiple compute units and pass each one to a different iterator - /// QueryDefinition queryDefinition = new QueryDefinition("select * from ToDos t where t.cost > @expensive") - /// .WithParameter("@expensive", 9000); - /// using (FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - /// feedRanges[0], - /// queryDefinition, - /// null, - /// new QueryRequestOptions() { })) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) - /// { - /// using (StreamReader sr = new StreamReader(response.Content)) - /// using (JsonTextReader jtr = new JsonTextReader(sr)) - /// { - /// JObject result = JObject.Load(jtr); - /// } - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetItemQueryStreamIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. - /// For more information on preparing SQL statements with parameterized values, please see . - /// - /// A FeedRange obtained from . - /// The Cosmos SQL query definition. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// An iterator to go through the items. - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition - /// - /// feedRanges = await this.Container.GetFeedRangesAsync(); - /// // Distribute feedRanges across multiple compute units and pass each one to a different iterator - /// QueryDefinition queryDefinition = new QueryDefinition("select * from ToDos t where t.cost > @expensive") - /// .WithParameter("@expensive", 9000); - /// using (FeedIterator feedIterator = this.Container.GetItemQueryIterator( - /// feedRanges[0], - /// queryDefinition, - /// null, - /// new QueryRequestOptions() { })) - /// { - /// while (feedIterator.HasMoreResults) - /// { - /// foreach(var item in await feedIterator.ReadNextAsync()) - /// { - /// Console.WriteLine(item.cost); - /// } - /// } - /// } - /// ]]> - /// - /// - public abstract FeedIterator GetItemQueryIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null); - - /// - /// This method creates a LINQ query for items under a container in an Azure Cosmos DB service. - /// IQueryable extension method ToFeedIterator() should be use for asynchronous execution with FeedIterator, please refer to example 2. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// LINQ execution is synchronous which will cause issues related to blocking calls. - /// It is recommended to always use ToFeedIterator(), and to do the asynchronous execution. - /// - /// The type of object to query. - /// (Optional)the option which allows the query to be executed synchronously via IOrderedQueryable. - /// (Optional) The continuation token in the Azure Cosmos DB service. - /// (Optional) The options for the item query request. - /// (Optional) The options to configure Linq Serializer Properties. This overrides properties in CosmosSerializerOptions while creating client - /// (Optional) An IOrderedQueryable{T} that can evaluate the query. - /// - /// 1. This example below shows LINQ query generation and blocked execution. - /// - /// (true) - /// .Where(b => b.Title == "War and Peace") - /// .AsEnumerable() - /// .FirstOrDefault(); - /// - /// // Query a nested property - /// Book otherBook = container.GetItemLinqQueryable(true) - /// .Where(b => b.Author.FirstName == "Leo") - /// .AsEnumerable() - /// .FirstOrDefault(); - /// - /// // Perform iteration on books - /// foreach (Book matchingBook in container.GetItemLinqQueryable(true) - /// .Where(b => b.Price > 100)) - /// { - /// // Iterate through books - /// } - /// ]]> - /// - /// - /// - /// 2. This example below shows LINQ query generation and asynchronous execution with FeedIterator. - /// - /// setIterator = container.GetItemLinqQueryable() - /// .Where(b => b.Title == "War and Peace") - /// .ToFeedIterator()) - /// { - /// //Asynchronous query execution - /// while (setIterator.HasMoreResults) - /// { - /// foreach(var item in await setIterator.ReadNextAsync()) - /// { - /// Console.WriteLine(item.Price); - /// } - /// } - /// } - /// ]]> - /// - /// - /// - /// The Azure Cosmos DB LINQ provider compiles LINQ to SQL statements. Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-linq-to-sql for the list of expressions supported by the Azure Cosmos DB LINQ provider. ToString() on the generated IQueryable returns the translated SQL statement. The Azure Cosmos DB provider translates JSON.NET and DataContract serialization attributes for members to their JSON property names. - /// - /// - public abstract IOrderedQueryable GetItemLinqQueryable( - bool allowSynchronousQueryExecution = false, - string continuationToken = null, - QueryRequestOptions requestOptions = null, - CosmosLinqSerializerOptions linqSerializerOptions = null); - - /// - /// Delegate to receive the changes within a execution. - /// - /// The changes that happened. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the changes. - public delegate Task ChangesHandler( - IReadOnlyCollection changes, - CancellationToken cancellationToken); - - /// - /// Delegate to receive the estimation of pending changes to be read by the associated instance. - /// - /// An estimation in number of transactions. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the estimation. - /// - /// The estimation over the Change Feed identifies volumes of transactions. If operations in the container are performed through stored procedures, transactional batch or bulk, a group of operations may share the same transaction scope and represented by a single transaction. - /// In those cases, the estimation might not exactly represent number of items, but it is still valid to understand if the pending volume is increasing, decreasing, or on a steady state. - /// - public delegate Task ChangesEstimationHandler( - long estimatedPendingChanges, - CancellationToken cancellationToken); - - /// - /// Initializes a for change feed processing. - /// - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive changes. - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate); - - /// - /// Initializes a for change feed monitoring. - /// - /// The name of the Processor the Estimator is going to measure. - /// Delegate to receive estimation. - /// Time interval on which to report the estimation. Default is 5 seconds. - /// - /// The goal of the Estimator is to measure progress of a particular processor. In order to do that, the and other parameters, like the leases container, need to match that of the Processor to measure. - /// - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( - string processorName, - ChangesEstimationHandler estimationDelegate, - TimeSpan? estimationPeriod = null); - - /// - /// Gets a for change feed monitoring. - /// - /// The name of the Processor the Estimator is going to measure. - /// Instance of a Cosmos Container that holds the leases. - /// - /// The goal of the Estimator is to measure progress of a particular processor. In order to do that, the and other parameters, like the leases container, need to match that of the Processor to measure. - /// - /// An instance of - public abstract ChangeFeedEstimator GetChangeFeedEstimator( - string processorName, - Container leaseContainer); - - /// - /// Initializes a new instance of - /// that can be used to perform operations across multiple items - /// in the container with the provided partition key in a transactional manner. - /// - /// The partition key for all items in the batch. - /// A new instance of . - /// - /// Limits on TransactionalBatch requests - /// - public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); - - /// - /// Obtains a list of that can be used to parallelize Feed operations. - /// - /// (Optional) representing request cancellation. - /// A list of . - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken = default); - - /// - /// This method creates an iterator to consume a Change Feed. - /// - /// Where to start the changefeed from. - /// Defines the mode on which to consume the change feed. - /// (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. - public abstract FeedIterator GetChangeFeedStreamIterator( - ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// This method creates an iterator to consume a Change Feed. - /// - /// Where to start the changefeed from. - /// Defines the mode on which to consume the change feed. - /// (Optional) The options for the Change Feed consumption. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - /// - /// - /// feedIterator = this.Container.GetChangeFeedIterator( - /// ChangeFeedStartFrom.Beginning(), - /// ChangeFeedMode.Incremental, - /// options); - /// - /// while (feedIterator.HasMoreResults) - /// { - /// FeedResponse response = await feedIterator.ReadNextAsync(); - /// - /// if (response.StatusCode == NotModified) - /// { - /// // No new changes - /// // Capture response.ContinuationToken and break or sleep for some time - /// } - /// else - /// { - /// foreach (var item in response) - /// { - /// Console.WriteLine(item); - /// } - /// } - /// } - /// ]]> - /// - /// - /// An iterator to go through the Change Feed. - public abstract FeedIterator GetChangeFeedIterator( - ChangeFeedStartFrom changeFeedStartFrom, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = null); - - /// - /// Delegate to receive the changes within a execution. - /// - /// The context related to the changes. - /// The changes that happened. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the changes. - public delegate Task ChangeFeedHandler( - ChangeFeedProcessorContext context, - IReadOnlyCollection changes, - CancellationToken cancellationToken); - - /// - /// Delegate to receive the changes within a execution with manual checkpoint. - /// - /// The context related to the changes. - /// The changes that happened. - /// A task representing an asynchronous checkpoint on the progress of a lease. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the changes. - /// - /// - /// changes, Func checkpointAsync, CancellationToken cancellationToken) => - /// { - /// // consume changes - /// - /// // On certain condition, we can checkpoint - /// await checkpointAsync(); - /// } - /// ]]> - /// - /// - public delegate Task ChangeFeedHandlerWithManualCheckpoint( - ChangeFeedProcessorContext context, - IReadOnlyCollection changes, - Func checkpointAsync, - CancellationToken cancellationToken); - - /// - /// Delegate to receive the changes within a execution. - /// - /// The context related to the changes. - /// The changes that happened. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the changes. - public delegate Task ChangeFeedStreamHandler( - ChangeFeedProcessorContext context, - Stream changes, - CancellationToken cancellationToken); - - /// - /// Delegate to receive the changes within a execution with manual checkpoint. - /// - /// The context related to the changes. - /// The changes that happened. - /// A task representing an asynchronous checkpoint on the progress of a lease. - /// A cancellation token representing the current cancellation status of the instance. - /// A representing the asynchronous operation that is going to be done with the changes. - /// - /// - /// checkpointAsync, CancellationToken cancellationToken) => - /// { - /// // consume stream - /// - /// // On certain condition, we can checkpoint - /// await checkpointAsync(); - /// } - /// ]]> - /// - /// - public delegate Task ChangeFeedStreamHandlerWithManualCheckpoint( - ChangeFeedProcessorContext context, - Stream changes, - Func checkpointAsync, - CancellationToken cancellationToken); - - /// - /// Delegate to notify errors during change feed operations. - /// - /// A unique identifier for the lease. - /// The exception that happened. - /// A representing the asynchronous operation that is going to be done with the notification. - /// - /// - /// - /// { - /// if (exception is ChangeFeedProcessorUserException userException) - /// { - /// Console.WriteLine($"Current instance's delegate had an unhandled when processing lease {leaseToken}."); - /// Console.WriteLine($"Diagnostics {userException.ExceptionContext.Diagnostics}"); - /// Console.WriteLine($"Headers {userException.ExceptionContext.Headers}"); - /// Console.WriteLine(userException.ToString()); - /// } - /// else - /// { - /// Console.WriteLine($"Current instance faced an exception when processing lease {leaseToken}."); - /// Console.WriteLine(exception.ToString()); - /// } - /// - /// return Task.CompletedTask; - /// } - /// ]]> - /// - /// - public delegate Task ChangeFeedMonitorErrorDelegate( - string leaseToken, - Exception exception); - - /// - /// Delegate to notify events of leases being acquired by a change feed processor. - /// - /// A unique identifier for the lease. - /// A representing the asynchronous operation that is going to be done with the notification. - /// - /// - /// - /// { - /// Console.WriteLine($"Current instance released lease {leaseToken} and stopped processing it."); - /// - /// return Task.CompletedTask; - /// } - /// ]]> - /// - /// - public delegate Task ChangeFeedMonitorLeaseAcquireDelegate(string leaseToken); - - /// - /// Delegate to notify events of leases being releases by a change feed processor. - /// - /// A unique identifier for the lease. - /// A representing the asynchronous operation that is going to be done with the notification. - /// - /// - /// - /// { - /// Console.WriteLine($"Current instance acquired lease {leaseToken} and will start processing it."); - /// - /// return Task.CompletedTask; - /// } - /// ]]> - /// - /// - public delegate Task ChangeFeedMonitorLeaseReleaseDelegate(string leaseToken); - - /// - /// Initializes a for change feed processing. - /// - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive changes. - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedHandler onChangesDelegate); - - /// - /// Initializes a for change feed processing with manual checkpoint. - /// - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive changes. - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedHandlerWithManualCheckpoint onChangesDelegate); - - /// - /// Initializes a for change feed processing. - /// - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive changes. - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedStreamHandler onChangesDelegate); - - /// - /// Initializes a for change feed processing with manual checkpoint. - /// - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive changes. - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Operations for reading, replacing, or deleting a specific, existing container or item in a container by id. + /// There are two different types of operations. + /// 1. The object operations where it serializes and deserializes the item on request/response + /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream + /// See for creating new containers, and reading/querying all containers. + /// + /// + /// Note: all these operations make calls against a fixed budget. + /// You should design your system such that these calls scale sub linearly with your application. + /// For instance, do not call `container.readAsync()` before every single `container.readItemAsync()` call to ensure the container exists; + /// do this once on application start up. + /// + public abstract class Container + { + /// + /// The Id of the Cosmos container + /// + public abstract string Id { get; } + + /// + /// Returns the parent Database reference + /// + public abstract Database Database { get; } + + /// + /// Returns the conflicts + /// + public abstract Conflicts Conflicts { get; } + + /// + /// Returns the scripts + /// + public abstract Scripts.Scripts Scripts { get; } + + /// + /// Reads a from the Azure Cosmos service as an asynchronous operation. + /// + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the read resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// + /// + /// + public abstract Task ReadContainerAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads a from the Azure Cosmos service as an asynchronous operation. + /// + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// + /// A containing a containing the read resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// + /// + /// + /// + public abstract Task ReadContainerStreamAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Replace a from the Azure Cosmos service as an asynchronous operation. + /// + /// The object. + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the replace resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// Update the container to disable automatic indexing + /// + /// + /// + /// + public abstract Task ReplaceContainerAsync( + ContainerProperties containerProperties, + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Replace a from the Azure Cosmos service as an asynchronous operation. + /// + /// The . + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// + /// A containing a containing the replace resource record. + /// + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// + /// + /// + public abstract Task ReplaceContainerStreamAsync( + ContainerProperties containerProperties, + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Delete a from the Azure Cosmos DB service as an asynchronous operation. + /// + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// A containing a which will contain information about the request issued. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// + /// + /// + public abstract Task DeleteContainerAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Delete a from the Azure Cosmos DB service as an asynchronous operation. + /// + /// (Optional) The options for the container request. + /// (Optional) representing request cancellation. + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// + /// + /// + /// + /// A containing a which will contain information about the request issued. + public abstract Task DeleteContainerStreamAsync( + ContainerRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Gets container throughput in measurement of request units per second in the Azure Cosmos service. + /// + /// (Optional) representing request cancellation. + /// Provisioned throughput in request units per second + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// The provisioned throughput for this container. + /// + /// + /// + /// Null value indicates a container with no throughput provisioned. + /// + /// + /// + /// The following example shows how to get the throughput. + /// + /// + /// + /// + /// Request Units + /// Set throughput on a container + public abstract Task ReadThroughputAsync( + CancellationToken cancellationToken = default); + + /// + /// Gets container throughput in measurement of request units per second in the Azure Cosmos service. + /// + /// The options for the throughput request. + /// (Optional) representing request cancellation. + /// The throughput response + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// The provisioned throughput for this container. + /// + /// + /// The following example shows how to get the throughput + /// + /// + /// + /// + /// + /// The following example shows how to get throughput, MinThroughput and is replace in progress + /// + /// + /// + /// + /// Request Units + /// Set throughput on a container + public abstract Task ReadThroughputAsync( + RequestOptions requestOptions, + CancellationToken cancellationToken = default); + + /// + /// Sets throughput provisioned for a container in measurement of request units per second in the Azure Cosmos service. + /// + /// The Cosmos container throughput, expressed in Request Units per second. + /// (Optional) The options for the throughput request. + /// (Optional) representing request cancellation. + /// The throughput response. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// The provisioned throughput for this container. + /// + /// + /// The following example shows how to get the throughput. + /// + /// + /// + /// + /// Request Units + /// Set throughput on a container + public abstract Task ReplaceThroughputAsync( + int throughput, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Sets throughput provisioned for a container in measurement of request units per second in the Azure Cosmos service. + /// + /// The Cosmos container throughput expressed in Request Units per second. + /// (Optional) The options for the throughput request. + /// (Optional) representing request cancellation. + /// The throughput response. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// The following example shows how to replace the fixed throughput. + /// + /// + /// + /// + /// + /// The following example shows how to replace the autoscale provisioned throughput + /// + /// + /// + /// + /// + /// Request Units + /// Set throughput on a container + /// + public abstract Task ReplaceThroughputAsync( + ThroughputProperties throughputProperties, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Creates a Item as an asynchronous operation in the Azure Cosmos service. + /// + /// A containing the payload. + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// The that was created contained within a object representing the service response for the asynchronous operation. + /// + /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// This example creates an item in a Cosmos container. + /// + /// + /// + /// + public abstract Task CreateItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Creates a item as an asynchronous operation in the Azure Cosmos service. + /// + /// A JSON serializable object that must contain an id property. to implement a custom serializer + /// for the item. If not specified will be populated by extracting from {T} + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// The that was created contained within a object representing the service response for the asynchronous operation. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// (test, new PartitionKey(test.status)); + /// ]]> + /// + /// + public abstract Task> CreateItemAsync( + T item, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads a item from the Azure Cosmos service as an asynchronous operation. + /// + /// The Cosmos item id + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the read resource record. + /// + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. + /// + /// + /// Read a response as a stream. + /// + /// + /// + /// + public abstract Task ReadItemStreamAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads a item from the Azure Cosmos service as an asynchronous operation. + /// + /// The Cosmos item id + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps the read resource record. + /// + /// + /// Items contain meta data that can be obtained by mapping these meta data attributes to properties in . + /// * "_ts": Gets the last modified time stamp associated with the item from the Azure Cosmos DB service. + /// * "_etag": Gets the entity tag associated with the item from the Azure Cosmos DB service. + /// * "ttl": Gets the time to live in seconds of the item in the Azure Cosmos DB service. + /// Note that, this API does not support the usage of property at the moment. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); + /// + /// Example 2: Reading Item with Implicit Casting + /// + /// This example shows how to read an item from the container and implicitly cast the + /// response directly to a `ToDoActivity` object, omitting the metadata in the `ItemResponse`. + /// + /// ToDoActivity toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); + /// + /// ]]> + /// + /// + public abstract Task> ReadItemAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Upserts an item stream as an asynchronous operation in the Azure Cosmos service. + /// + /// A containing the payload. + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the read resource record. + /// + /// + /// The Stream operation only throws on client side exceptions. + /// This is to increase performance and prevent the overhead of throwing exceptions. + /// Check the HTTP status code on the response to check if the operation failed. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// + /// Upsert result i.e. creation or replace can be identified by the status code: + /// 201 - item created + /// 200 - item replaced + /// + /// + /// + /// Upsert a Stream containing the item to Cosmos + /// + /// + /// + /// + public abstract Task UpsertItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Upserts an item as an asynchronous operation in the Azure Cosmos service. + /// + /// A JSON serializable object that must contain an id property. to implement a custom serializer + /// for the item. If not specified will be populated by extracting from {T} + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// The that was upserted contained within a object representing the service response for the asynchronous operation. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// Upsert result i.e. creation or replace can be identified by the status code: + /// 201 - item created + /// 200 - item replaced + /// + /// + /// + /// + /// item = await this.container.UpsertItemAsync(test, new PartitionKey(test.status)); + /// ]]> + /// + /// + public abstract Task> UpsertItemAsync( + T item, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Replaces a item in the Azure Cosmos service as an asynchronous operation. + /// + /// + /// The item's partition key value is immutable. + /// To change an item's partition key value you must delete the original item and insert a new item. + /// + /// A containing the payload. + /// The Cosmos item id + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the replace resource record. + /// + /// + /// The Stream operation only throws on client side exceptions. + /// This is to increase performance and prevent the overhead of throwing exceptions. + /// Check the HTTP status code on the response to check if the operation failed. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// Replace an item in Cosmos + /// + /// + /// + /// + public abstract Task ReplaceItemStreamAsync( + Stream streamPayload, + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Replaces a item in the Azure Cosmos service as an asynchronous operation. + /// + /// + /// The item's partition key value is immutable. + /// To change an item's partition key value you must delete the original item and insert a new item. + /// + /// A JSON serializable object that must contain an id property. to implement a custom serializer. + /// The Cosmos item id of the existing item. + /// for the item. If not specified will be populated by extracting from {T} + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps the updated resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// (test, test.id, new PartitionKey(test.status)); + /// ]]> + /// + /// + public abstract Task> ReplaceItemAsync( + T item, + string id, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads multiple items from a container using Id and PartitionKey values. + /// + /// List of item.Id and + /// Request Options for ReadMany Operation + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the response. + /// + /// is meant to perform better latency-wise than a query with IN statements to fetch a large number of independent items. + /// + /// + /// itemList = new List<(string, PartitionKey)> + /// { + /// ("Id1", new PartitionKey("pkValue1")), + /// ("Id2", new PartitionKey("pkValue2")), + /// ("Id3", new PartitionKey("pkValue3")) + /// }; + /// + /// using (ResponseMessage response = await this.Container.ReadManyItemsStreamAsync(itemList)) + /// { + /// if (!response.IsSuccessStatusCode) + /// { + /// //Handle and log exception + /// return; + /// } + /// + /// //Read or do other operations with the stream + /// using (StreamReader streamReader = new StreamReader(response.Content)) + /// { + /// string content = streamReader.ReadToEndAsync(); + /// } + /// + /// } + /// ]]> + /// + /// + public abstract Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads multiple items from a container using Id and PartitionKey values. + /// + /// List of item.Id and + /// Request Options for ReadMany Operation + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps the typed items. + /// + /// is meant to perform better latency-wise than a query with IN statements to fetch a large number of independent items. + /// + /// + /// itemList = new List<(string, PartitionKey)> + /// { + /// ("Id1", new PartitionKey("pkValue1")), + /// ("Id2", new PartitionKey("pkValue2")), + /// ("Id3", new PartitionKey("pkValue3")) + /// }; + /// + /// FeedResponse feedResponse = this.Container.ReadManyItemsAsync(itemList); + /// ]]> + /// + /// + public abstract Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Patches an item in the Azure Cosmos service as an asynchronous operation. + /// + /// + /// The item's partition key value is immutable. + /// To change an item's partition key value you must delete the original item and insert a new item. + /// The patch operations are atomic and are executed sequentially. + /// By default, resource body will be returned as part of the response. User can request no content by setting flag to false. + /// Note that, this API does not support the usage of property at the moment. + /// + /// The Cosmos item id of the item to be patched. + /// for the item + /// Represents a list of operations to be sequentially applied to the referred Cosmos item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps the updated resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); + /// + /// Example 2: Reading Item with Implicit Casting + /// + /// This example shows how to read an item from the container and implicitly cast the + /// response directly to a `ToDoActivity` object, omitting the metadata in the `ItemResponse`. + /// + /// ToDoActivity toDoActivity = await this.container.ReadItemAsync("id", new PartitionKey("partitionKey")); + /// + /// /* toDoActivity = { + /// "id" : "someId", + /// "status" : "someStatusPK", + /// "description" : "someDescription", + /// "frequency" : 7 + /// }*/ + /// + /// List patchOperations = new List() + /// { + /// PatchOperation.Add("/daysOfWeek", new string[]{"Monday", "Thursday"}), + /// PatchOperation.Replace("/frequency", 2), + /// PatchOperation.Remove("/description") + /// }; + /// + /// ItemResponse item = await this.container.PatchItemAsync(toDoActivity.id, new PartitionKey(toDoActivity.status), patchOperations); + /// /* item.Resource = { + /// "id" : "someId", + /// "status" : "someStatusPK", + /// "description" : null, + /// "frequency" : 2, + /// "daysOfWeek" : ["Monday", "Thursday"] + /// }*/ + /// ]]> + /// + /// + /// Supported partial document update modes + public abstract Task> PatchItemAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Patches an item in the Azure Cosmos service as an asynchronous operation. + /// + /// + /// The item's partition key value is immutable. + /// To change an item's partition key value you must delete the original item and insert a new item. + /// The patch operations are atomic and are executed sequentially. + /// By default, resource body will be returned as part of the response. User can request no content by setting flag to false. + /// + /// The Cosmos item id + /// The partition key for the item. + /// Represents a list of operations to be sequentially applied to the referred Cosmos item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the patched resource record. + /// + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// This is to increase performance and prevent the overhead of throwing exceptions. + /// Check the HTTP status code on the response to check if the operation failed. + /// + /// + /// + /// + /// Supported partial document update modes + public abstract Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Delete a item from the Azure Cosmos service as an asynchronous operation. + /// + /// The Cosmos item id + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the delete resource record. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// For delete operations, the will be null. Item content is not expected in the response. + /// + /// The Stream operation only throws on client side exceptions. This is to increase performance and prevent the overhead of throwing exceptions. Check the HTTP status code on the response to check if the operation failed. + /// + /// + /// Delete an item from Cosmos + /// + /// + /// + /// + public abstract Task DeleteItemStreamAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Delete a item from the Azure Cosmos service as an asynchronous operation. + /// + /// The Cosmos item id + /// The partition key for the item. + /// (Optional) The options for the item request. + /// (Optional) representing request cancellation. + /// A containing a which will contain information about the request issued. + /// + /// is always null + /// + /// For delete operations, the will be null. Item content is not expected in the response. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// ("id", new PartitionKey("partitionKey")); + /// ]]> + /// + /// + public abstract Task> DeleteItemAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. + /// For more information on preparing SQL statements with parameterized values, please see . + /// + /// The Cosmos SQL query definition. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition + /// + /// @expensive") + /// .WithParameter("@expensive", 9000); + /// using (FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + /// queryDefinition, + /// null, + /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) + /// { + /// using (StreamReader sr = new StreamReader(response.Content)) + /// using (JsonTextReader jtr = new JsonTextReader(sr)) + /// { + /// JObject result = JObject.Load(jtr); + /// } + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract FeedIterator GetItemQueryStreamIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. + /// For more information on preparing SQL statements with parameterized values, please see . + /// + /// The Cosmos SQL query definition. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// Create a query to get all the ToDoActivity that have a cost greater than 9000 + /// + /// @expensive") + /// .WithParameter("@expensive", 9000); + /// using (FeedIterator feedIterator = this.Container.GetItemQueryIterator( + /// queryDefinition, + /// null, + /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// foreach(var item in await feedIterator.ReadNextAsync()) + /// { + /// Console.WriteLine(item.cost); + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract FeedIterator GetItemQueryIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement. It returns a FeedIterator. + /// + /// The Cosmos SQL query text. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// 1. Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition + /// + /// 9000", + /// null, + /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) + /// { + /// using (StreamReader sr = new StreamReader(response.Content)) + /// using (JsonTextReader jtr = new JsonTextReader(sr)) + /// { + /// JObject result = JObject.Load(jtr); + /// } + /// } + /// } + /// } + /// ]]> + /// + /// + /// + /// 2. Creates a FeedIterator to get all the ToDoActivity. + /// + /// + /// + /// + public abstract FeedIterator GetItemQueryStreamIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement. It returns a FeedIterator. + /// + /// The Cosmos SQL query text. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// 1. Create a query to get all the ToDoActivity that have a cost greater than 9000 + /// + /// feedIterator = this.Container.GetItemQueryIterator( + /// "select * from ToDos t where t.cost > 9000", + /// null, + /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// foreach(var item in await feedIterator.ReadNextAsync()) + /// { + /// Console.WriteLine(item.cost); + /// } + /// } + /// } + /// ]]> + /// + /// + /// + /// 2. Create a FeedIterator to get all the ToDoActivity. + /// + /// feedIterator = this.Container.GetItemQueryIterator( + /// null, + /// null, + /// new QueryRequestOptions() { PartitionKey = new PartitionKey("Error")})) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// foreach(var item in await feedIterator.ReadNextAsync()) + /// { + /// Console.WriteLine(item.cost); + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract FeedIterator GetItemQueryIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. + /// For more information on preparing SQL statements with parameterized values, please see . + /// + /// A FeedRange obtained from + /// The Cosmos SQL query definition. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#stream-api + /// + /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition + /// + /// feedRanges = await this.Container.GetFeedRangesAsync(); + /// // Distribute feedRanges across multiple compute units and pass each one to a different iterator + /// QueryDefinition queryDefinition = new QueryDefinition("select * from ToDos t where t.cost > @expensive") + /// .WithParameter("@expensive", 9000); + /// using (FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + /// feedRanges[0], + /// queryDefinition, + /// null, + /// new QueryRequestOptions() { })) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// using (ResponseMessage response = await feedIterator.ReadNextAsync()) + /// { + /// using (StreamReader sr = new StreamReader(response.Content)) + /// using (JsonTextReader jtr = new JsonTextReader(sr)) + /// { + /// JObject result = JObject.Load(jtr); + /// } + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract FeedIterator GetItemQueryStreamIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a query for items under a container in an Azure Cosmos database using a SQL statement with parameterized values. It returns a FeedIterator. + /// For more information on preparing SQL statements with parameterized values, please see . + /// + /// A FeedRange obtained from . + /// The Cosmos SQL query definition. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// An iterator to go through the items. + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// Create a query to get all the ToDoActivity that have a cost greater than 9000 for the specified partition + /// + /// feedRanges = await this.Container.GetFeedRangesAsync(); + /// // Distribute feedRanges across multiple compute units and pass each one to a different iterator + /// QueryDefinition queryDefinition = new QueryDefinition("select * from ToDos t where t.cost > @expensive") + /// .WithParameter("@expensive", 9000); + /// using (FeedIterator feedIterator = this.Container.GetItemQueryIterator( + /// feedRanges[0], + /// queryDefinition, + /// null, + /// new QueryRequestOptions() { })) + /// { + /// while (feedIterator.HasMoreResults) + /// { + /// foreach(var item in await feedIterator.ReadNextAsync()) + /// { + /// Console.WriteLine(item.cost); + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract FeedIterator GetItemQueryIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null); + + /// + /// This method creates a LINQ query for items under a container in an Azure Cosmos DB service. + /// IQueryable extension method ToFeedIterator() should be use for asynchronous execution with FeedIterator, please refer to example 2. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// LINQ execution is synchronous which will cause issues related to blocking calls. + /// It is recommended to always use ToFeedIterator(), and to do the asynchronous execution. + /// + /// The type of object to query. + /// (Optional)the option which allows the query to be executed synchronously via IOrderedQueryable. + /// (Optional) The continuation token in the Azure Cosmos DB service. + /// (Optional) The options for the item query request. + /// (Optional) The options to configure Linq Serializer Properties. This overrides properties in CosmosSerializerOptions while creating client + /// (Optional) An IOrderedQueryable{T} that can evaluate the query. + /// + /// 1. This example below shows LINQ query generation and blocked execution. + /// + /// (true) + /// .Where(b => b.Title == "War and Peace") + /// .AsEnumerable() + /// .FirstOrDefault(); + /// + /// // Query a nested property + /// Book otherBook = container.GetItemLinqQueryable(true) + /// .Where(b => b.Author.FirstName == "Leo") + /// .AsEnumerable() + /// .FirstOrDefault(); + /// + /// // Perform iteration on books + /// foreach (Book matchingBook in container.GetItemLinqQueryable(true) + /// .Where(b => b.Price > 100)) + /// { + /// // Iterate through books + /// } + /// ]]> + /// + /// + /// + /// 2. This example below shows LINQ query generation and asynchronous execution with FeedIterator. + /// + /// setIterator = container.GetItemLinqQueryable() + /// .Where(b => b.Title == "War and Peace") + /// .ToFeedIterator()) + /// { + /// //Asynchronous query execution + /// while (setIterator.HasMoreResults) + /// { + /// foreach(var item in await setIterator.ReadNextAsync()) + /// { + /// Console.WriteLine(item.Price); + /// } + /// } + /// } + /// ]]> + /// + /// + /// + /// The Azure Cosmos DB LINQ provider compiles LINQ to SQL statements. Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-linq-to-sql for the list of expressions supported by the Azure Cosmos DB LINQ provider. ToString() on the generated IQueryable returns the translated SQL statement. The Azure Cosmos DB provider translates JSON.NET and DataContract serialization attributes for members to their JSON property names. + /// + /// + public abstract IOrderedQueryable GetItemLinqQueryable( + bool allowSynchronousQueryExecution = false, + string continuationToken = null, + QueryRequestOptions requestOptions = null, + CosmosLinqSerializerOptions linqSerializerOptions = null); + + /// + /// Delegate to receive the changes within a execution. + /// + /// The changes that happened. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the changes. + public delegate Task ChangesHandler( + IReadOnlyCollection changes, + CancellationToken cancellationToken); + + /// + /// Delegate to receive the estimation of pending changes to be read by the associated instance. + /// + /// An estimation in number of transactions. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the estimation. + /// + /// The estimation over the Change Feed identifies volumes of transactions. If operations in the container are performed through stored procedures, transactional batch or bulk, a group of operations may share the same transaction scope and represented by a single transaction. + /// In those cases, the estimation might not exactly represent number of items, but it is still valid to understand if the pending volume is increasing, decreasing, or on a steady state. + /// + public delegate Task ChangesEstimationHandler( + long estimatedPendingChanges, + CancellationToken cancellationToken); + + /// + /// Initializes a for change feed processing. + /// + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive changes. + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate); + + /// + /// Initializes a for change feed monitoring. + /// + /// The name of the Processor the Estimator is going to measure. + /// Delegate to receive estimation. + /// Time interval on which to report the estimation. Default is 5 seconds. + /// + /// The goal of the Estimator is to measure progress of a particular processor. In order to do that, the and other parameters, like the leases container, need to match that of the Processor to measure. + /// + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( + string processorName, + ChangesEstimationHandler estimationDelegate, + TimeSpan? estimationPeriod = null); + + /// + /// Gets a for change feed monitoring. + /// + /// The name of the Processor the Estimator is going to measure. + /// Instance of a Cosmos Container that holds the leases. + /// + /// The goal of the Estimator is to measure progress of a particular processor. In order to do that, the and other parameters, like the leases container, need to match that of the Processor to measure. + /// + /// An instance of + public abstract ChangeFeedEstimator GetChangeFeedEstimator( + string processorName, + Container leaseContainer); + + /// + /// Initializes a new instance of + /// that can be used to perform operations across multiple items + /// in the container with the provided partition key in a transactional manner. + /// + /// The partition key for all items in the batch. + /// A new instance of . + /// + /// Limits on TransactionalBatch requests + /// + public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); + + /// + /// Obtains a list of that can be used to parallelize Feed operations. + /// + /// (Optional) representing request cancellation. + /// A list of . + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken = default); + + /// + /// This method creates an iterator to consume a Change Feed. + /// + /// Where to start the changefeed from. + /// Defines the mode on which to consume the change feed. + /// (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. + public abstract FeedIterator GetChangeFeedStreamIterator( + ChangeFeedStartFrom changeFeedStartFrom, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = null); + + /// + /// This method creates an iterator to consume a Change Feed. + /// + /// Where to start the changefeed from. + /// Defines the mode on which to consume the change feed. + /// (Optional) The options for the Change Feed consumption. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + /// + /// + /// feedIterator = this.Container.GetChangeFeedIterator( + /// ChangeFeedStartFrom.Beginning(), + /// ChangeFeedMode.Incremental, + /// options); + /// + /// while (feedIterator.HasMoreResults) + /// { + /// FeedResponse response = await feedIterator.ReadNextAsync(); + /// + /// if (response.StatusCode == NotModified) + /// { + /// // No new changes + /// // Capture response.ContinuationToken and break or sleep for some time + /// } + /// else + /// { + /// foreach (var item in response) + /// { + /// Console.WriteLine(item); + /// } + /// } + /// } + /// ]]> + /// + /// + /// An iterator to go through the Change Feed. + public abstract FeedIterator GetChangeFeedIterator( + ChangeFeedStartFrom changeFeedStartFrom, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = null); + + /// + /// Delegate to receive the changes within a execution. + /// + /// The context related to the changes. + /// The changes that happened. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the changes. + public delegate Task ChangeFeedHandler( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + CancellationToken cancellationToken); + + /// + /// Delegate to receive the changes within a execution with manual checkpoint. + /// + /// The context related to the changes. + /// The changes that happened. + /// A task representing an asynchronous checkpoint on the progress of a lease. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the changes. + /// + /// + /// changes, Func checkpointAsync, CancellationToken cancellationToken) => + /// { + /// // consume changes + /// + /// // On certain condition, we can checkpoint + /// await checkpointAsync(); + /// } + /// ]]> + /// + /// + public delegate Task ChangeFeedHandlerWithManualCheckpoint( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + Func checkpointAsync, + CancellationToken cancellationToken); + + /// + /// Delegate to receive the changes within a execution. + /// + /// The context related to the changes. + /// The changes that happened. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the changes. + public delegate Task ChangeFeedStreamHandler( + ChangeFeedProcessorContext context, + Stream changes, + CancellationToken cancellationToken); + + /// + /// Delegate to receive the changes within a execution with manual checkpoint. + /// + /// The context related to the changes. + /// The changes that happened. + /// A task representing an asynchronous checkpoint on the progress of a lease. + /// A cancellation token representing the current cancellation status of the instance. + /// A representing the asynchronous operation that is going to be done with the changes. + /// + /// + /// checkpointAsync, CancellationToken cancellationToken) => + /// { + /// // consume stream + /// + /// // On certain condition, we can checkpoint + /// await checkpointAsync(); + /// } + /// ]]> + /// + /// + public delegate Task ChangeFeedStreamHandlerWithManualCheckpoint( + ChangeFeedProcessorContext context, + Stream changes, + Func checkpointAsync, + CancellationToken cancellationToken); + + /// + /// Delegate to notify errors during change feed operations. + /// + /// A unique identifier for the lease. + /// The exception that happened. + /// A representing the asynchronous operation that is going to be done with the notification. + /// + /// + /// + /// { + /// if (exception is ChangeFeedProcessorUserException userException) + /// { + /// Console.WriteLine($"Current instance's delegate had an unhandled when processing lease {leaseToken}."); + /// Console.WriteLine($"Diagnostics {userException.ExceptionContext.Diagnostics}"); + /// Console.WriteLine($"Headers {userException.ExceptionContext.Headers}"); + /// Console.WriteLine(userException.ToString()); + /// } + /// else + /// { + /// Console.WriteLine($"Current instance faced an exception when processing lease {leaseToken}."); + /// Console.WriteLine(exception.ToString()); + /// } + /// + /// return Task.CompletedTask; + /// } + /// ]]> + /// + /// + public delegate Task ChangeFeedMonitorErrorDelegate( + string leaseToken, + Exception exception); + + /// + /// Delegate to notify events of leases being acquired by a change feed processor. + /// + /// A unique identifier for the lease. + /// A representing the asynchronous operation that is going to be done with the notification. + /// + /// + /// + /// { + /// Console.WriteLine($"Current instance released lease {leaseToken} and stopped processing it."); + /// + /// return Task.CompletedTask; + /// } + /// ]]> + /// + /// + public delegate Task ChangeFeedMonitorLeaseAcquireDelegate(string leaseToken); + + /// + /// Delegate to notify events of leases being releases by a change feed processor. + /// + /// A unique identifier for the lease. + /// A representing the asynchronous operation that is going to be done with the notification. + /// + /// + /// + /// { + /// Console.WriteLine($"Current instance acquired lease {leaseToken} and will start processing it."); + /// + /// return Task.CompletedTask; + /// } + /// ]]> + /// + /// + public delegate Task ChangeFeedMonitorLeaseReleaseDelegate(string leaseToken); + + /// + /// Initializes a for change feed processing. + /// + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive changes. + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate); + + /// + /// Initializes a for change feed processing with manual checkpoint. + /// + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive changes. + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate); + + /// + /// Initializes a for change feed processing. + /// + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive changes. + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate); + + /// + /// Initializes a for change feed processing with manual checkpoint. + /// + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive changes. + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); #if PREVIEW - /// - /// Deletes all items in the Container with the specified value. - /// Starts an asynchronous Cosmos DB background operation which deletes all items in the Container with the specified value. - /// The asynchronous Cosmos DB background operation runs using a percentage of user RUs. - /// - /// The of the items to be deleted. - /// (Optional) The options for the Partition Key Delete request. - /// (Optional) representing request cancellation. - /// - /// A containing a . - /// - public abstract Task DeleteAllItemsByPartitionKeyStreamAsync( - Cosmos.PartitionKey partitionKey, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - - /// - /// Gets the list of Partition Key Range identifiers for a . - /// - /// A - /// (Optional) representing request cancellation. - /// The list of Partition Key Range identifiers affected by a particular FeedRange. - /// - /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api - public abstract Task> GetPartitionKeyRangesAsync( - FeedRange feedRange, - CancellationToken cancellationToken = default); - - /// - /// Initializes a for change feed processing with all versions and deletes. - /// - /// Document type - /// A name that identifies the Processor and the particular work it will do. - /// Delegate to receive all changes and deletes - /// - /// - /// > documents, CancellationToken token) => - /// { - /// Console.WriteLine($"number of documents processed: {documents.Count}"); - /// - /// string id = default; - /// string pk = default; - /// string description = default; - /// - /// foreach (ChangeFeedItem changeFeedItem in documents) - /// { - /// if (changeFeedItem.Metadata.OperationType != ChangeFeedOperationType.Delete) - /// { - /// id = changeFeedItem.Current.id.ToString(); - /// pk = changeFeedItem.Current.pk.ToString(); - /// description = changeFeedItem.Current.description.ToString(); - /// } - /// else - /// { - /// id = changeFeedItem.Previous.id.ToString(); - /// pk = changeFeedItem.Previous.pk.ToString(); - /// description = changeFeedItem.Previous.description.ToString(); - /// } - /// - /// ChangeFeedOperationType operationType = changeFeedItem.Metadata.OperationType; - /// long previousLsn = changeFeedItem.Metadata.PreviousLsn; - /// DateTime conflictResolutionTimestamp = changeFeedItem.Metadata.ConflictResolutionTimestamp; - /// long lsn = changeFeedItem.Metadata.Lsn; - /// bool isTimeToLiveExpired = changeFeedItem.Metadata.IsTimeToLiveExpired; - /// } - /// - /// return Task.CompletedTask; - /// }) - /// .WithInstanceName(Guid.NewGuid().ToString()) - /// .WithLeaseContainer(leaseContainer) - /// .WithErrorNotification((leaseToken, error) => - /// { - /// Console.WriteLine(error.ToString()); - /// - /// return Task.CompletedTask; - /// }) - /// .Build(); - /// - /// await changeFeedProcessor.StartAsync(); - /// await Task.Delay(1000); - /// await this.Container.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); - /// await this.Container.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); - /// await this.Container.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); - /// - /// allProcessedDocumentsEvent.WaitOne(10 * 1000); - /// - /// await changeFeedProcessor.StopAsync(); - /// ]]> - /// - /// - /// An instance of - public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - string processorName, + /// + /// Deletes all items in the Container with the specified value. + /// Starts an asynchronous Cosmos DB background operation which deletes all items in the Container with the specified value. + /// The asynchronous Cosmos DB background operation runs using a percentage of user RUs. + /// + /// The of the items to be deleted. + /// (Optional) The options for the Partition Key Delete request. + /// (Optional) representing request cancellation. + /// + /// A containing a . + /// + public abstract Task DeleteAllItemsByPartitionKeyStreamAsync( + Cosmos.PartitionKey partitionKey, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Gets the list of Partition Key Range identifiers for a . + /// + /// A + /// (Optional) representing request cancellation. + /// The list of Partition Key Range identifiers affected by a particular FeedRange. + /// + /// https://aka.ms/cosmosdb-dot-net-exceptions#typed-api + public abstract Task> GetPartitionKeyRangesAsync( + FeedRange feedRange, + CancellationToken cancellationToken = default); + + /// + /// Initializes a for change feed processing with all versions and deletes. + /// + /// Document type + /// A name that identifies the Processor and the particular work it will do. + /// Delegate to receive all changes and deletes + /// + /// + /// > documents, CancellationToken token) => + /// { + /// Console.WriteLine($"number of documents processed: {documents.Count}"); + /// + /// string id = default; + /// string pk = default; + /// string description = default; + /// + /// foreach (ChangeFeedItem changeFeedItem in documents) + /// { + /// if (changeFeedItem.Metadata.OperationType != ChangeFeedOperationType.Delete) + /// { + /// id = changeFeedItem.Current.id.ToString(); + /// pk = changeFeedItem.Current.pk.ToString(); + /// description = changeFeedItem.Current.description.ToString(); + /// } + /// else + /// { + /// id = changeFeedItem.Previous.id.ToString(); + /// pk = changeFeedItem.Previous.pk.ToString(); + /// description = changeFeedItem.Previous.description.ToString(); + /// } + /// + /// ChangeFeedOperationType operationType = changeFeedItem.Metadata.OperationType; + /// long previousLsn = changeFeedItem.Metadata.PreviousLsn; + /// DateTime conflictResolutionTimestamp = changeFeedItem.Metadata.ConflictResolutionTimestamp; + /// long lsn = changeFeedItem.Metadata.Lsn; + /// bool isTimeToLiveExpired = changeFeedItem.Metadata.IsTimeToLiveExpired; + /// } + /// + /// return Task.CompletedTask; + /// }) + /// .WithInstanceName(Guid.NewGuid().ToString()) + /// .WithLeaseContainer(leaseContainer) + /// .WithErrorNotification((leaseToken, error) => + /// { + /// Console.WriteLine(error.ToString()); + /// + /// return Task.CompletedTask; + /// }) + /// .Build(); + /// + /// await changeFeedProcessor.StartAsync(); + /// await Task.Delay(1000); + /// await this.Container.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); + /// await this.Container.UpsertItemAsync(new { id = "1", pk = "1", description = "test after replace" }, partitionKey: new PartitionKey("1")); + /// await this.Container.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); + /// + /// allProcessedDocumentsEvent.WaitOne(10 * 1000); + /// + /// await changeFeedProcessor.StopAsync(); + /// ]]> + /// + /// + /// An instance of + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + string processorName, ChangeFeedHandler> onChangesDelegate); /// - /// Determines whether the given child feed range is a part of the specified parent feed range. + /// Determines whether a feed range contains another feed range. + /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. - /// - /// The feed range representing the parent range. - /// The feed range representing the child range. - /// A token to cancel the operation if needed. - /// - /// - /// - /// - /// - /// True if the child feed range is a subset of the parent feed range; otherwise, false. + /// + /// The feed range representing the parent range. + /// The feed range representing the child range. + /// A token to cancel the operation if needed. + /// + /// + /// + /// + /// + /// True if the child feed range is a subset of the parent feed range; otherwise, false. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange x, Cosmos.FeedRange y, @@ -1817,5 +1818,5 @@ public virtual Task IsFeedRangePartOfAsync( throw new NotImplementedException(); } #endif - } + } } \ No newline at end of file From 23bdc17dad9bb34f58cdcc9c3d93a161488bc280 Mon Sep 17 00:00:00 2001 From: Philip Thomas <86612891+philipthomas-MSFT@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:20:56 -0400 Subject: [PATCH 140/145] Update Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs Co-authored-by: Kiran Kumar Kolli --- .../Resource/Container/ContainerCore.Items.cs | 2767 +++++++++-------- 1 file changed, 1384 insertions(+), 1383 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 712e3153ac..bd77817445 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1,1261 +1,1261 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; using System.Linq; using System.Numerics; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed; - using Microsoft.Azure.Cosmos.ChangeFeed.FeedProcessing; - using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; - using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Json; - using Microsoft.Azure.Cosmos.Linq; - using Microsoft.Azure.Cosmos.Pagination; - using Microsoft.Azure.Cosmos.Query; - using Microsoft.Azure.Cosmos.Query.Core; - using Microsoft.Azure.Cosmos.Query.Core.Monads; - using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.ReadFeed; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; + using Microsoft.Azure.Cosmos.ChangeFeed.FeedProcessing; + using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; + using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Linq; + using Microsoft.Azure.Cosmos.Pagination; + using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Query.Core.Monads; + using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; - using Microsoft.Azure.Cosmos.Serializer; - using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Serializer; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; - /// - /// Used to perform operations on items. There are two different types of operations. - /// 1. The object operations where it serializes and deserializes the item on request/response - /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream - /// - internal abstract partial class ContainerCore : ContainerInternal - { - /// - /// Cache the full URI segment without the last resource id. - /// This allows only a single con-cat operation instead of building the full URI string each time. - /// - private string cachedUriSegmentWithoutId { get; } - - private readonly CosmosQueryClient queryClient; - - public async Task CreateItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - streamPayload: streamPayload, - operationType: OperationType.Create, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> CreateItemAsync( - T item, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - item: item, - operationType: OperationType.Create, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task ReadItemStreamAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Read, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> ReadItemAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage response = await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Read, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task UpsertItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - streamPayload: streamPayload, - operationType: OperationType.Upsert, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> UpsertItemAsync( - T item, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - item: item, - operationType: OperationType.Upsert, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task ReplaceItemStreamAsync( - Stream streamPayload, - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: streamPayload, - operationType: OperationType.Replace, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> ReplaceItemAsync( - T item, - string id, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - item: item, - operationType: OperationType.Replace, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task DeleteItemStreamAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Delete, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> DeleteItemAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage response = await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Delete, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public override FeedIterator GetItemQueryStreamIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - QueryDefinition queryDefinition = null; - if (queryText != null) - { - queryDefinition = new QueryDefinition(queryText); - } - - return this.GetItemQueryStreamIterator( - queryDefinition, - continuationToken, - requestOptions); - } - - public override FeedIterator GetItemQueryStreamIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return this.GetItemQueryStreamIteratorInternal( - sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), - isContinuationExcpected: true, - continuationToken: continuationToken, - feedRange: null, - requestOptions: requestOptions); - } - - public async Task ReadManyItemsStreamAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ITrace trace, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - PartitionKeyDefinition partitionKeyDefinition; - try - { - partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - } - catch (CosmosException ex) - { - return ex.ToCosmosResponseMessage(request: null); - } - - ReadManyHelper readManyHelper = new ReadManyQueryHelper(partitionKeyDefinition, - this); - - return await readManyHelper.ExecuteReadManyRequestAsync(items, - readManyRequestOptions, - trace, - cancellationToken); - } - - public async Task> ReadManyItemsAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ITrace trace, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), - this); - - return await readManyHelper.ExecuteReadManyRequestAsync(items, - readManyRequestOptions, - trace, - cancellationToken); - } - - public override FeedIterator GetItemQueryIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - QueryDefinition queryDefinition = null; - if (queryText != null) - { - queryDefinition = new QueryDefinition(queryText); - } - - return this.GetItemQueryIterator( - queryDefinition, - continuationToken, - requestOptions); - } - - public override FeedIterator GetItemQueryIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (requestOptions.IsEffectivePartitionKeyRouting) - { - requestOptions.PartitionKey = null; - } - - if (!(this.GetItemQueryStreamIterator( - queryDefinition, - continuationToken, - requestOptions) is FeedIteratorInternal feedIterator)) - { - throw new InvalidOperationException($"Expected a FeedIteratorInternal."); - } - - return new FeedIteratorCore( - feedIterator: feedIterator, - responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); - } - - public override IOrderedQueryable GetItemLinqQueryable( - bool allowSynchronousQueryExecution = false, - string continuationToken = null, - QueryRequestOptions requestOptions = null, - CosmosLinqSerializerOptions linqSerializerOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (this.ClientContext.ClientOptions != null) - { - linqSerializerOptions ??= new CosmosLinqSerializerOptions - { + /// + /// Used to perform operations on items. There are two different types of operations. + /// 1. The object operations where it serializes and deserializes the item on request/response + /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream + /// + internal abstract partial class ContainerCore : ContainerInternal + { + /// + /// Cache the full URI segment without the last resource id. + /// This allows only a single con-cat operation instead of building the full URI string each time. + /// + private string cachedUriSegmentWithoutId { get; } + + private readonly CosmosQueryClient queryClient; + + public async Task CreateItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + streamPayload: streamPayload, + operationType: OperationType.Create, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> CreateItemAsync( + T item, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + item: item, + operationType: OperationType.Create, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task ReadItemStreamAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Read, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> ReadItemAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage response = await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Read, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task UpsertItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + streamPayload: streamPayload, + operationType: OperationType.Upsert, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> UpsertItemAsync( + T item, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + item: item, + operationType: OperationType.Upsert, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task ReplaceItemStreamAsync( + Stream streamPayload, + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: streamPayload, + operationType: OperationType.Replace, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> ReplaceItemAsync( + T item, + string id, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + item: item, + operationType: OperationType.Replace, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task DeleteItemStreamAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Delete, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> DeleteItemAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage response = await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Delete, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public override FeedIterator GetItemQueryStreamIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + QueryDefinition queryDefinition = null; + if (queryText != null) + { + queryDefinition = new QueryDefinition(queryText); + } + + return this.GetItemQueryStreamIterator( + queryDefinition, + continuationToken, + requestOptions); + } + + public override FeedIterator GetItemQueryStreamIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return this.GetItemQueryStreamIteratorInternal( + sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), + isContinuationExcpected: true, + continuationToken: continuationToken, + feedRange: null, + requestOptions: requestOptions); + } + + public async Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + PartitionKeyDefinition partitionKeyDefinition; + try + { + partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); + } + catch (CosmosException ex) + { + return ex.ToCosmosResponseMessage(request: null); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(partitionKeyDefinition, + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + + public async Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + + public override FeedIterator GetItemQueryIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + QueryDefinition queryDefinition = null; + if (queryText != null) + { + queryDefinition = new QueryDefinition(queryText); + } + + return this.GetItemQueryIterator( + queryDefinition, + continuationToken, + requestOptions); + } + + public override FeedIterator GetItemQueryIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (requestOptions.IsEffectivePartitionKeyRouting) + { + requestOptions.PartitionKey = null; + } + + if (!(this.GetItemQueryStreamIterator( + queryDefinition, + continuationToken, + requestOptions) is FeedIteratorInternal feedIterator)) + { + throw new InvalidOperationException($"Expected a FeedIteratorInternal."); + } + + return new FeedIteratorCore( + feedIterator: feedIterator, + responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); + } + + public override IOrderedQueryable GetItemLinqQueryable( + bool allowSynchronousQueryExecution = false, + string continuationToken = null, + QueryRequestOptions requestOptions = null, + CosmosLinqSerializerOptions linqSerializerOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (this.ClientContext.ClientOptions != null) + { + linqSerializerOptions ??= new CosmosLinqSerializerOptions + { PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default - }; - } - - CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); - - return new CosmosLinqQuery( - this, - this.ClientContext.ResponseFactory, - (CosmosQueryClientCore)this.queryClient, - continuationToken, - requestOptions, - allowSynchronousQueryExecution, - linqSerializerOptionsInternal); - } - - public override FeedIterator GetItemQueryIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (!(this.GetItemQueryStreamIterator( - feedRange, - queryDefinition, - continuationToken, - requestOptions) is FeedIteratorInternal feedIterator)) - { - throw new InvalidOperationException($"Expected a FeedIteratorInternal."); - } - - return new FeedIteratorCore( - feedIterator: feedIterator, - responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); - } - - public override FeedIterator GetItemQueryStreamIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; - return this.GetItemQueryStreamIteratorInternal( - sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), - isContinuationExcpected: true, - continuationToken: continuationToken, - feedRange: feedRangeInternal, - requestOptions: requestOptions); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: true); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedStreamHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate), - withManualCheckpointing: true); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, - ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( - string processorName, - ChangesEstimationHandler estimationDelegate, - TimeSpan? estimationPeriod = null) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (estimationDelegate == null) - { - throw new ArgumentNullException(nameof(estimationDelegate)); - } - - ChangeFeedEstimatorRunner changeFeedEstimatorCore = new ChangeFeedEstimatorRunner(estimationDelegate, estimationPeriod); - return new ChangeFeedProcessorBuilder( - processorName: processorName, - container: this, - changeFeedProcessor: changeFeedEstimatorCore, - applyBuilderConfiguration: changeFeedEstimatorCore.ApplyBuildConfiguration); - } - - public override ChangeFeedEstimator GetChangeFeedEstimator( - string processorName, - Container leaseContainer) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (leaseContainer == null) - { - throw new ArgumentNullException(nameof(leaseContainer)); - } - - return new ChangeFeedEstimatorCore( - processorName: processorName, - monitoredContainer: this, - leaseContainer: (ContainerInternal)leaseContainer, - documentServiceLeaseContainer: default); - } - - public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) - { - return new BatchCore(this, partitionKey); - } - - public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( - ChangeFeedCrossFeedRangeState state, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = default) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + }; + } + + CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); + + return new CosmosLinqQuery( + this, + this.ClientContext.ResponseFactory, + (CosmosQueryClientCore)this.queryClient, + continuationToken, + requestOptions, + allowSynchronousQueryExecution, + linqSerializerOptionsInternal); + } + + public override FeedIterator GetItemQueryIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (!(this.GetItemQueryStreamIterator( + feedRange, + queryDefinition, + continuationToken, + requestOptions) is FeedIteratorInternal feedIterator)) + { + throw new InvalidOperationException($"Expected a FeedIteratorInternal."); + } + + return new FeedIteratorCore( + feedIterator: feedIterator, + responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); + } + + public override FeedIterator GetItemQueryStreamIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; + return this.GetItemQueryStreamIteratorInternal( + sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), + isContinuationExcpected: true, + continuationToken: continuationToken, + feedRange: feedRangeInternal, + requestOptions: requestOptions); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: true); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate), + withManualCheckpointing: true); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, + ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( + string processorName, + ChangesEstimationHandler estimationDelegate, + TimeSpan? estimationPeriod = null) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (estimationDelegate == null) + { + throw new ArgumentNullException(nameof(estimationDelegate)); + } + + ChangeFeedEstimatorRunner changeFeedEstimatorCore = new ChangeFeedEstimatorRunner(estimationDelegate, estimationPeriod); + return new ChangeFeedProcessorBuilder( + processorName: processorName, + container: this, + changeFeedProcessor: changeFeedEstimatorCore, + applyBuilderConfiguration: changeFeedEstimatorCore.ApplyBuildConfiguration); + } + + public override ChangeFeedEstimator GetChangeFeedEstimator( + string processorName, + Container leaseContainer) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (leaseContainer == null) + { + throw new ArgumentNullException(nameof(leaseContainer)); + } + + return new ChangeFeedEstimatorCore( + processorName: processorName, + monitoredContainer: this, + leaseContainer: (ContainerInternal)leaseContainer, + documentServiceLeaseContainer: default); + } + + public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) + { + return new BatchCore(this, partitionKey); + } + + public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( + ChangeFeedCrossFeedRangeState state, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = default) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - changeFeedRequestOptions: changeFeedRequestOptions); - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - + Guid.NewGuid(), + changeFeedRequestOptions: changeFeedRequestOptions); + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + Dictionary additionalHeaders; - if ((changeFeedRequestOptions?.Properties != null) && changeFeedRequestOptions.Properties.Any()) - { - Dictionary additionalNonStringHeaders = new Dictionary(); - additionalHeaders = new Dictionary(); - foreach (KeyValuePair keyValuePair in changeFeedRequestOptions.Properties) - { - if (keyValuePair.Value is string stringValue) - { - additionalHeaders[keyValuePair.Key] = stringValue; - } - else - { - additionalNonStringHeaders[keyValuePair.Key] = keyValuePair.Value; - } - } - - changeFeedRequestOptions.Properties = additionalNonStringHeaders; - } - else - { - additionalHeaders = null; - } - - ChangeFeedExecutionOptions changeFeedPaginationOptions = new ChangeFeedExecutionOptions( - changeFeedMode, - changeFeedRequestOptions?.PageSizeHint, - changeFeedRequestOptions?.JsonSerializationFormatOptions?.JsonSerializationFormat, - additionalHeaders); - - return new ChangeFeedCrossFeedRangeAsyncEnumerable( - documentContainer, - state, - changeFeedPaginationOptions, - changeFeedRequestOptions?.JsonSerializationFormatOptions); - } - - public override FeedIterator GetStandByFeedIterator( - string continuationToken = null, - int? maxItemCount = null, - StandByFeedIteratorRequestOptions requestOptions = null) - { - StandByFeedIteratorRequestOptions cosmosQueryRequestOptions = requestOptions ?? new StandByFeedIteratorRequestOptions(); - - return new StandByFeedIteratorCore( - clientContext: this.ClientContext, - continuationToken: continuationToken, - maxItemCount: maxItemCount, - container: this, - options: cosmosQueryRequestOptions); - } - - /// - /// Helper method to create a stream feed iterator. - /// It decides if it is a query or read feed and create - /// the correct instance. - /// - public override FeedIteratorInternal GetItemQueryStreamIteratorInternal( - SqlQuerySpec sqlQuerySpec, - bool isContinuationExcpected, - string continuationToken, - FeedRangeInternal feedRange, - QueryRequestOptions requestOptions) - { - requestOptions ??= new QueryRequestOptions(); - - if (requestOptions.IsEffectivePartitionKeyRouting) - { - if (feedRange != null) - { - throw new ArgumentException(nameof(feedRange), ClientResources.FeedToken_EffectivePartitionKeyRouting); - } - - requestOptions.PartitionKey = null; - } - - if (sqlQuerySpec == null) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + if ((changeFeedRequestOptions?.Properties != null) && changeFeedRequestOptions.Properties.Any()) + { + Dictionary additionalNonStringHeaders = new Dictionary(); + additionalHeaders = new Dictionary(); + foreach (KeyValuePair keyValuePair in changeFeedRequestOptions.Properties) + { + if (keyValuePair.Value is string stringValue) + { + additionalHeaders[keyValuePair.Key] = stringValue; + } + else + { + additionalNonStringHeaders[keyValuePair.Key] = keyValuePair.Value; + } + } + + changeFeedRequestOptions.Properties = additionalNonStringHeaders; + } + else + { + additionalHeaders = null; + } + + ChangeFeedExecutionOptions changeFeedPaginationOptions = new ChangeFeedExecutionOptions( + changeFeedMode, + changeFeedRequestOptions?.PageSizeHint, + changeFeedRequestOptions?.JsonSerializationFormatOptions?.JsonSerializationFormat, + additionalHeaders); + + return new ChangeFeedCrossFeedRangeAsyncEnumerable( + documentContainer, + state, + changeFeedPaginationOptions, + changeFeedRequestOptions?.JsonSerializationFormatOptions); + } + + public override FeedIterator GetStandByFeedIterator( + string continuationToken = null, + int? maxItemCount = null, + StandByFeedIteratorRequestOptions requestOptions = null) + { + StandByFeedIteratorRequestOptions cosmosQueryRequestOptions = requestOptions ?? new StandByFeedIteratorRequestOptions(); + + return new StandByFeedIteratorCore( + clientContext: this.ClientContext, + continuationToken: continuationToken, + maxItemCount: maxItemCount, + container: this, + options: cosmosQueryRequestOptions); + } + + /// + /// Helper method to create a stream feed iterator. + /// It decides if it is a query or read feed and create + /// the correct instance. + /// + public override FeedIteratorInternal GetItemQueryStreamIteratorInternal( + SqlQuerySpec sqlQuerySpec, + bool isContinuationExcpected, + string continuationToken, + FeedRangeInternal feedRange, + QueryRequestOptions requestOptions) + { + requestOptions ??= new QueryRequestOptions(); + + if (requestOptions.IsEffectivePartitionKeyRouting) + { + if (feedRange != null) + { + throw new ArgumentException(nameof(feedRange), ClientResources.FeedToken_EffectivePartitionKeyRouting); + } + + requestOptions.PartitionKey = null; + } + + if (sqlQuerySpec == null) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - requestOptions); - - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((requestOptions.Properties != null) && requestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: requestOptions.MaxItemCount ?? int.MaxValue); - - return new ReadFeedIteratorCore( - documentContainer, - continuationToken, - readFeedPaginationOptions, - requestOptions, - this, - cancellationToken: default); - } - - return QueryIterator.Create( - containerCore: this, - client: this.queryClient, - clientContext: this.ClientContext, - sqlQuerySpec: sqlQuerySpec, - continuationToken: continuationToken, - feedRangeInternal: feedRange, - queryRequestOptions: requestOptions, - resourceLink: this.LinkUri, - isContinuationExpected: isContinuationExcpected, - allowNonValueAggregateQuery: true, - partitionedQueryExecutionInfo: null, - resourceType: ResourceType.Document); - } - - public override FeedIteratorInternal GetReadFeedIterator( - QueryDefinition queryDefinition, - QueryRequestOptions queryRequestOptions, - string resourceLink, - ResourceType resourceType, - string continuationToken, - int pageSize) - { - queryRequestOptions ??= new QueryRequestOptions(); - - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + Guid.NewGuid(), + requestOptions); + + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((requestOptions.Properties != null) && requestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: requestOptions.MaxItemCount ?? int.MaxValue); + + return new ReadFeedIteratorCore( + documentContainer, + continuationToken, + readFeedPaginationOptions, + requestOptions, + this, + cancellationToken: default); + } + + return QueryIterator.Create( + containerCore: this, + client: this.queryClient, + clientContext: this.ClientContext, + sqlQuerySpec: sqlQuerySpec, + continuationToken: continuationToken, + feedRangeInternal: feedRange, + queryRequestOptions: requestOptions, + resourceLink: this.LinkUri, + isContinuationExpected: isContinuationExcpected, + allowNonValueAggregateQuery: true, + partitionedQueryExecutionInfo: null, + resourceType: ResourceType.Document); + } + + public override FeedIteratorInternal GetReadFeedIterator( + QueryDefinition queryDefinition, + QueryRequestOptions queryRequestOptions, + string resourceLink, + ResourceType resourceType, + string continuationToken, + int pageSize) + { + queryRequestOptions ??= new QueryRequestOptions(); + + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, + this.queryClient, + Guid.NewGuid(), + queryRequestOptions, + resourceLink: resourceLink, + resourceType: resourceType); + + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + FeedIteratorInternal feedIterator; + if (queryDefinition != null) + { + feedIterator = QueryIterator.Create( + containerCore: this, + client: this.queryClient, + clientContext: this.ClientContext, + sqlQuerySpec: queryDefinition.ToSqlQuerySpec(), + continuationToken: continuationToken, + feedRangeInternal: FeedRangeEpk.FullRange, + queryRequestOptions: queryRequestOptions, + resourceLink: resourceLink, + isContinuationExpected: false, + allowNonValueAggregateQuery: true, + partitionedQueryExecutionInfo: null, + resourceType: resourceType); + } + else + { + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((queryRequestOptions.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: queryRequestOptions.MaxItemCount ?? int.MaxValue); + + feedIterator = new ReadFeedIteratorCore( + documentContainer: documentContainer, + queryRequestOptions: queryRequestOptions, + continuationToken: continuationToken, + readFeedPaginationOptions: readFeedPaginationOptions, + container: this, + cancellationToken: default); + } + + return feedIterator; + } + + public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( + ReadFeedCrossFeedRangeState state, + QueryRequestOptions queryRequestOptions = default) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - queryRequestOptions, - resourceLink: resourceLink, - resourceType: resourceType); - - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - FeedIteratorInternal feedIterator; - if (queryDefinition != null) - { - feedIterator = QueryIterator.Create( - containerCore: this, - client: this.queryClient, - clientContext: this.ClientContext, - sqlQuerySpec: queryDefinition.ToSqlQuerySpec(), - continuationToken: continuationToken, - feedRangeInternal: FeedRangeEpk.FullRange, - queryRequestOptions: queryRequestOptions, - resourceLink: resourceLink, - isContinuationExpected: false, - allowNonValueAggregateQuery: true, - partitionedQueryExecutionInfo: null, - resourceType: resourceType); - } - else - { - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((queryRequestOptions.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: queryRequestOptions.MaxItemCount ?? int.MaxValue); - - feedIterator = new ReadFeedIteratorCore( - documentContainer: documentContainer, - queryRequestOptions: queryRequestOptions, - continuationToken: continuationToken, - readFeedPaginationOptions: readFeedPaginationOptions, - container: this, - cancellationToken: default); - } - - return feedIterator; - } - - public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( - ReadFeedCrossFeedRangeState state, - QueryRequestOptions queryRequestOptions = default) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, - this.queryClient, - Guid.NewGuid(), - queryRequestOptions); - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((queryRequestOptions?.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: queryRequestOptions?.MaxItemCount); - - return new ReadFeedCrossFeedRangeAsyncEnumerable( - documentContainer, - state, - readFeedPaginationOptions); - } - - // Extracted partition key might be invalid as CollectionCache might be stale. - // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy - // and partition-key is extracted again. - private async Task ExtractPartitionKeyAndProcessItemStreamAsync( - PartitionKey? partitionKey, - string itemId, - T item, - OperationType operationType, - ItemRequestOptions requestOptions, - ITrace trace, - CancellationToken cancellationToken) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - Stream itemStream; - using (trace.StartChild("ItemSerialize")) - { - itemStream = this.ClientContext.SerializerCore.ToStream(item); - } - - // User specified PK value, no need to extract it - if (partitionKey.HasValue) - { - return await this.ProcessItemStreamAsync( - partitionKey, - itemId, - itemStream, - operationType, - requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - PartitionKeyMismatchRetryPolicy requestRetryPolicy = null; - while (true) - { - partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, trace, cancellationToken); - - ResponseMessage responseMessage = await this.ProcessItemStreamAsync( - partitionKey, - itemId, - itemStream, - operationType, - requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - if (responseMessage.IsSuccessStatusCode) - { - return responseMessage; - } - - if (requestRetryPolicy == null) - { - requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( - await this.ClientContext.DocumentClient.GetCollectionCacheAsync(trace), - requestRetryPolicy); - } - - ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); - if (!retryResult.ShouldRetry) - { - return responseMessage; - } - } - } - - private async Task ProcessItemStreamAsync( - PartitionKey? partitionKey, - string itemId, - Stream streamPayload, - OperationType operationType, - ItemRequestOptions requestOptions, - ITrace trace, - CancellationToken cancellationToken) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting) - { - partitionKey = null; - } - - ContainerInternal.ValidatePartitionKey(partitionKey, requestOptions); - string resourceUri = this.GetResourceUri(requestOptions, operationType, itemId); - - ResponseMessage responseMessage = await this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: resourceUri, - resourceType: ResourceType.Document, - operationType: operationType, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: partitionKey, - itemId: itemId, - streamPayload: streamPayload, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - - return responseMessage; - } - - public override async Task GetPartitionKeyValueFromStreamAsync( - Stream stream, - ITrace trace, - CancellationToken cancellation = default) - { - if (!stream.CanSeek) - { - throw new ArgumentException("Stream needs to be seekable", nameof(stream)); - } - - using (ITrace childTrace = trace.StartChild("Get PkValue From Stream", TraceComponent.Routing, Tracing.TraceLevel.Info)) - { - try - { - stream.Position = 0; - - if (!(stream is MemoryStream memoryStream)) - { - memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - } - - // TODO: Avoid copy - IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); - IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); - CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); - - IReadOnlyList> tokenslist = await this.GetPartitionKeyPathTokensAsync(childTrace, cancellation); - List cosmosElementList = new List(tokenslist.Count); - - foreach (IReadOnlyList tokenList in tokenslist) - { - if (ContainerCore.TryParseTokenListForElement(pathTraversal, tokenList, out CosmosElement element)) - { - cosmosElementList.Add(element); - } - else - { - cosmosElementList.Add(null); - } - } - - return ContainerCore.CosmosElementToPartitionKeyObject(cosmosElementList); - } - finally - { - // MemoryStream casting leverage might change position - stream.Position = 0; - } - } - } - - public Task DeleteAllItemsByPartitionKeyStreamAsync( - Cosmos.PartitionKey partitionKey, - ITrace trace, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - PartitionKey? resultingPartitionKey = requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting ? null : (PartitionKey?)partitionKey; - ContainerCore.ValidatePartitionKey(resultingPartitionKey, requestOptions); - - return this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: this.LinkUri, - resourceType: ResourceType.PartitionKey, - operationType: OperationType.Delete, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: resultingPartitionKey, - itemId: null, - streamPayload: null, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - } - - private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IReadOnlyList tokens, out CosmosElement result) - { - result = null; - for (int i = 0; i < tokens.Count - 1; i++) - { - if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) - { - return false; - } - } - - if (!pathTraversal.TryGetValue(tokens[tokens.Count - 1], out result)) - { - return false; - } - - return true; - } - - private static PartitionKey CosmosElementToPartitionKeyObject(IReadOnlyList cosmosElementList) - { - PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); - - foreach (CosmosElement cosmosElement in cosmosElementList) - { - if (cosmosElement == null) - { - partitionKeyBuilder.AddNoneType(); - } - else - { - _ = cosmosElement switch - { - CosmosString cosmosString => partitionKeyBuilder.Add(cosmosString.Value), - CosmosNumber cosmosNumber => partitionKeyBuilder.Add(Number64.ToDouble(cosmosNumber.Value)), - CosmosBoolean cosmosBoolean => partitionKeyBuilder.Add(cosmosBoolean.Value), - CosmosNull _ => partitionKeyBuilder.AddNullValue(), - _ => throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - RMResources.UnsupportedPartitionKeyComponentValue, - cosmosElement)), - }; - } - } - - return partitionKeyBuilder.Build(); - } - - private string GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) - { - if (requestOptions != null && requestOptions.TryGetResourceUri(out Uri resourceUri)) - { - return resourceUri.OriginalString; - } - - switch (operationType) - { - case OperationType.Create: - case OperationType.Upsert: - return this.LinkUri; - - default: - return this.ContcatCachedUriWithId(itemId); - } - } - - /// - /// Gets the full resource segment URI without the last id. - /// - /// Example: /dbs/*/colls/*/{this.pathSegment}/ - private string GetResourceSegmentUriWithoutId() - { - // StringBuilder is roughly 2x faster than string.Format - StringBuilder stringBuilder = new StringBuilder(this.LinkUri.Length + - Paths.DocumentsPathSegment.Length + 2); - stringBuilder.Append(this.LinkUri); - stringBuilder.Append("/"); - stringBuilder.Append(Paths.DocumentsPathSegment); - stringBuilder.Append("/"); - return stringBuilder.ToString(); - } - - /// - /// Gets the full resource URI using the cached resource URI segment - /// - /// The resource id - /// - /// A document link in the format of {CachedUriSegmentWithoutId}/{0}/ with {0} being a Uri escaped version of the - /// - /// Would be used when creating an , or when replacing or deleting a item in Azure Cosmos DB. - /// - private string ContcatCachedUriWithId(string resourceId) - { - Debug.Assert(this.cachedUriSegmentWithoutId.EndsWith("/")); - return this.cachedUriSegmentWithoutId + Uri.EscapeUriString(resourceId); - } - - public async Task> PatchItemAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - ITrace trace, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage responseMessage = await this.PatchItemStreamAsync( - id, - partitionKey, - patchOperations, - trace, - requestOptions, - cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(responseMessage); - } - - public Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - ITrace trace, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException(nameof(id)); - } - - if (partitionKey == null) - { - throw new ArgumentNullException(nameof(partitionKey)); - } - - if (patchOperations == null || - !patchOperations.Any()) - { - throw new ArgumentNullException(nameof(patchOperations)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - Stream patchOperationsStream; - using (ITrace serializeTrace = trace.StartChild("Patch Operations Serialize")) - { - patchOperationsStream = this.ClientContext.SerializerCore.ToStream(new PatchSpec(patchOperations, requestOptions)); - } - - return this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: this.GetResourceUri( - requestOptions, - OperationType.Patch, - id), - resourceType: ResourceType.Document, - operationType: OperationType.Patch, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: partitionKey, - itemId: id, - streamPayload: patchOperationsStream, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - } - - public Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - Stream streamPayload, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (partitionKey == null) - { - throw new ArgumentNullException(nameof(partitionKey)); - } - - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - - if (streamPayload == null) - { - throw new ArgumentNullException(nameof(streamPayload)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - return this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: streamPayload, - operationType: OperationType.Patch, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - string processorName, - ChangeFeedHandler> onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.AllVersionsAndDeletes); - } - - private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( - string processorName, - ChangeFeedObserverFactory observerFactory, - ChangeFeedMode mode) - { - ChangeFeedProcessorCore changeFeedProcessor = new ChangeFeedProcessorCore(observerFactory); - return new ChangeFeedProcessorBuilder( - processorName: processorName, - container: this, - changeFeedProcessor: changeFeedProcessor, - applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); + Guid.NewGuid(), + queryRequestOptions); + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((queryRequestOptions?.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: queryRequestOptions?.MaxItemCount); + + return new ReadFeedCrossFeedRangeAsyncEnumerable( + documentContainer, + state, + readFeedPaginationOptions); + } + + // Extracted partition key might be invalid as CollectionCache might be stale. + // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy + // and partition-key is extracted again. + private async Task ExtractPartitionKeyAndProcessItemStreamAsync( + PartitionKey? partitionKey, + string itemId, + T item, + OperationType operationType, + ItemRequestOptions requestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + Stream itemStream; + using (trace.StartChild("ItemSerialize")) + { + itemStream = this.ClientContext.SerializerCore.ToStream(item); + } + + // User specified PK value, no need to extract it + if (partitionKey.HasValue) + { + return await this.ProcessItemStreamAsync( + partitionKey, + itemId, + itemStream, + operationType, + requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + PartitionKeyMismatchRetryPolicy requestRetryPolicy = null; + while (true) + { + partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, trace, cancellationToken); + + ResponseMessage responseMessage = await this.ProcessItemStreamAsync( + partitionKey, + itemId, + itemStream, + operationType, + requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + if (responseMessage.IsSuccessStatusCode) + { + return responseMessage; + } + + if (requestRetryPolicy == null) + { + requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( + await this.ClientContext.DocumentClient.GetCollectionCacheAsync(trace), + requestRetryPolicy); + } + + ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); + if (!retryResult.ShouldRetry) + { + return responseMessage; + } + } + } + + private async Task ProcessItemStreamAsync( + PartitionKey? partitionKey, + string itemId, + Stream streamPayload, + OperationType operationType, + ItemRequestOptions requestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting) + { + partitionKey = null; + } + + ContainerInternal.ValidatePartitionKey(partitionKey, requestOptions); + string resourceUri = this.GetResourceUri(requestOptions, operationType, itemId); + + ResponseMessage responseMessage = await this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: resourceUri, + resourceType: ResourceType.Document, + operationType: operationType, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: partitionKey, + itemId: itemId, + streamPayload: streamPayload, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + + return responseMessage; + } + + public override async Task GetPartitionKeyValueFromStreamAsync( + Stream stream, + ITrace trace, + CancellationToken cancellation = default) + { + if (!stream.CanSeek) + { + throw new ArgumentException("Stream needs to be seekable", nameof(stream)); + } + + using (ITrace childTrace = trace.StartChild("Get PkValue From Stream", TraceComponent.Routing, Tracing.TraceLevel.Info)) + { + try + { + stream.Position = 0; + + if (!(stream is MemoryStream memoryStream)) + { + memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + } + + // TODO: Avoid copy + IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); + IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); + CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); + + IReadOnlyList> tokenslist = await this.GetPartitionKeyPathTokensAsync(childTrace, cancellation); + List cosmosElementList = new List(tokenslist.Count); + + foreach (IReadOnlyList tokenList in tokenslist) + { + if (ContainerCore.TryParseTokenListForElement(pathTraversal, tokenList, out CosmosElement element)) + { + cosmosElementList.Add(element); + } + else + { + cosmosElementList.Add(null); + } + } + + return ContainerCore.CosmosElementToPartitionKeyObject(cosmosElementList); + } + finally + { + // MemoryStream casting leverage might change position + stream.Position = 0; + } + } + } + + public Task DeleteAllItemsByPartitionKeyStreamAsync( + Cosmos.PartitionKey partitionKey, + ITrace trace, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + PartitionKey? resultingPartitionKey = requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting ? null : (PartitionKey?)partitionKey; + ContainerCore.ValidatePartitionKey(resultingPartitionKey, requestOptions); + + return this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: this.LinkUri, + resourceType: ResourceType.PartitionKey, + operationType: OperationType.Delete, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: resultingPartitionKey, + itemId: null, + streamPayload: null, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + } + + private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IReadOnlyList tokens, out CosmosElement result) + { + result = null; + for (int i = 0; i < tokens.Count - 1; i++) + { + if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) + { + return false; + } + } + + if (!pathTraversal.TryGetValue(tokens[tokens.Count - 1], out result)) + { + return false; + } + + return true; + } + + private static PartitionKey CosmosElementToPartitionKeyObject(IReadOnlyList cosmosElementList) + { + PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); + + foreach (CosmosElement cosmosElement in cosmosElementList) + { + if (cosmosElement == null) + { + partitionKeyBuilder.AddNoneType(); + } + else + { + _ = cosmosElement switch + { + CosmosString cosmosString => partitionKeyBuilder.Add(cosmosString.Value), + CosmosNumber cosmosNumber => partitionKeyBuilder.Add(Number64.ToDouble(cosmosNumber.Value)), + CosmosBoolean cosmosBoolean => partitionKeyBuilder.Add(cosmosBoolean.Value), + CosmosNull _ => partitionKeyBuilder.AddNullValue(), + _ => throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + RMResources.UnsupportedPartitionKeyComponentValue, + cosmosElement)), + }; + } + } + + return partitionKeyBuilder.Build(); + } + + private string GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) + { + if (requestOptions != null && requestOptions.TryGetResourceUri(out Uri resourceUri)) + { + return resourceUri.OriginalString; + } + + switch (operationType) + { + case OperationType.Create: + case OperationType.Upsert: + return this.LinkUri; + + default: + return this.ContcatCachedUriWithId(itemId); + } + } + + /// + /// Gets the full resource segment URI without the last id. + /// + /// Example: /dbs/*/colls/*/{this.pathSegment}/ + private string GetResourceSegmentUriWithoutId() + { + // StringBuilder is roughly 2x faster than string.Format + StringBuilder stringBuilder = new StringBuilder(this.LinkUri.Length + + Paths.DocumentsPathSegment.Length + 2); + stringBuilder.Append(this.LinkUri); + stringBuilder.Append("/"); + stringBuilder.Append(Paths.DocumentsPathSegment); + stringBuilder.Append("/"); + return stringBuilder.ToString(); + } + + /// + /// Gets the full resource URI using the cached resource URI segment + /// + /// The resource id + /// + /// A document link in the format of {CachedUriSegmentWithoutId}/{0}/ with {0} being a Uri escaped version of the + /// + /// Would be used when creating an , or when replacing or deleting a item in Azure Cosmos DB. + /// + private string ContcatCachedUriWithId(string resourceId) + { + Debug.Assert(this.cachedUriSegmentWithoutId.EndsWith("/")); + return this.cachedUriSegmentWithoutId + Uri.EscapeUriString(resourceId); + } + + public async Task> PatchItemAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + ITrace trace, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage responseMessage = await this.PatchItemStreamAsync( + id, + partitionKey, + patchOperations, + trace, + requestOptions, + cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(responseMessage); + } + + public Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + ITrace trace, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (patchOperations == null || + !patchOperations.Any()) + { + throw new ArgumentNullException(nameof(patchOperations)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + Stream patchOperationsStream; + using (ITrace serializeTrace = trace.StartChild("Patch Operations Serialize")) + { + patchOperationsStream = this.ClientContext.SerializerCore.ToStream(new PatchSpec(patchOperations, requestOptions)); + } + + return this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: this.GetResourceUri( + requestOptions, + OperationType.Patch, + id), + resourceType: ResourceType.Document, + operationType: OperationType.Patch, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: partitionKey, + itemId: id, + streamPayload: patchOperationsStream, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + } + + public Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + Stream streamPayload, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (streamPayload == null) + { + throw new ArgumentNullException(nameof(streamPayload)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + return this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: streamPayload, + operationType: OperationType.Patch, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + string processorName, + ChangeFeedHandler> onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.AllVersionsAndDeletes); + } + + private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( + string processorName, + ChangeFeedObserverFactory observerFactory, + ChangeFeedMode mode) + { + ChangeFeedProcessorCore changeFeedProcessor = new ChangeFeedProcessorCore(observerFactory); + return new ChangeFeedProcessorBuilder( + processorName: processorName, + container: this, + changeFeedProcessor: changeFeedProcessor, + applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } /// @@ -1357,45 +1357,45 @@ public override async Task IsFeedRangePartOfAsync( } } - /// - /// Merges a list of feed ranges into a single range by taking the minimum value of the first range and the maximum value of the last range. - /// This function ensures that the resulting range covers the entire span of the input ranges. - /// - /// - The method begins by checking if the list contains only one range: - /// - If there is only one range, it simply returns that range without performing any additional logic. - /// - /// - If the list contains multiple ranges: - /// - It first sorts the ranges based on the minimum value of each range using a custom comparator (`MinComparer`). - /// - It selects the first range (after sorting) to extract the minimum value, ensuring the merged range starts with the lowest value across all ranges. - /// - It selects the last range (after sorting) to extract the maximum value, ensuring the merged range ends with the highest value across all ranges. - /// - /// - The inclusivity of the boundaries (`IsMinInclusive` and `IsMaxInclusive`) is inherited from the first range in the list: - /// - `IsMinInclusive` from the first range determines whether the merged range includes its minimum value. - /// - `IsMaxInclusive` from the last range would generally be expected to influence whether the merged range includes its maximum value, but this method uses `IsMaxInclusive` from the first range for both boundaries. - /// - **Note**: This could result in unexpected behavior if inclusivity should differ for the merged max value. - /// - /// - The merged range spans the minimum value of the first range and the maximum value of the last range, effectively combining multiple ranges into a single, continuous range. - /// - /// The list of feed ranges to merge. Each range contains a minimum and maximum value along with boundary inclusivity flags (`IsMinInclusive`, `IsMaxInclusive`). - /// - /// A new merged range with the minimum value from the first range and the maximum value from the last range. - /// If the list contains a single range, it returns that range directly without modification. - /// - /// - /// Thrown when the list of ranges is empty. - /// - /// - /// > ranges = new List> - /// { - /// new Documents.Routing.Range("A", "C", true, false), - /// new Documents.Routing.Range("D", "F", true, true), - /// new Documents.Routing.Range("G", "I", false, true), - /// }; - /// - /// Documents.Routing.Range mergedRange = MergeRanges(ranges); - /// // The merged range would span from "A" to "I", taking the minimum of the first range and the maximum of the last. - /// ]]> + /// + /// Merges a list of feed ranges into a single range by taking the minimum value of the first range and the maximum value of the last range. + /// This function ensures that the resulting range covers the entire span of the input ranges. + /// + /// - The method begins by checking if the list contains only one range: + /// - If there is only one range, it simply returns that range without performing any additional logic. + /// + /// - If the list contains multiple ranges: + /// - It first sorts the ranges based on the minimum value of each range using a custom comparator (`MinComparer`). + /// - It selects the first range (after sorting) to extract the minimum value, ensuring the merged range starts with the lowest value across all ranges. + /// - It selects the last range (after sorting) to extract the maximum value, ensuring the merged range ends with the highest value across all ranges. + /// + /// - The inclusivity of the boundaries (`IsMinInclusive` and `IsMaxInclusive`) is inherited from the first range in the list: + /// - `IsMinInclusive` from the first range determines whether the merged range includes its minimum value. + /// - `IsMaxInclusive` from the last range would generally be expected to influence whether the merged range includes its maximum value, but this method uses `IsMaxInclusive` from the first range for both boundaries. + /// - **Note**: This could result in unexpected behavior if inclusivity should differ for the merged max value. + /// + /// - The merged range spans the minimum value of the first range and the maximum value of the last range, effectively combining multiple ranges into a single, continuous range. + /// + /// The list of feed ranges to merge. Each range contains a minimum and maximum value along with boundary inclusivity flags (`IsMinInclusive`, `IsMaxInclusive`). + /// + /// A new merged range with the minimum value from the first range and the maximum value from the last range. + /// If the list contains a single range, it returns that range directly without modification. + /// + /// + /// Thrown when the list of ranges is empty. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range("A", "C", true, false), + /// new Documents.Routing.Range("D", "F", true, true), + /// new Documents.Routing.Range("G", "I", false, true), + /// }; + /// + /// Documents.Routing.Range mergedRange = MergeRanges(ranges); + /// // The merged range would span from "A" to "I", taking the minimum of the first range and the maximum of the last. + /// ]]> /// private static Documents.Routing.Range MergeRanges( List> ranges) @@ -1410,46 +1410,46 @@ private static Documents.Routing.Range MergeRanges( Documents.Routing.Range firstRange = ranges.First(); Documents.Routing.Range lastRange = ranges.Last(); - return new Documents.Routing.Range( - min: firstRange.Min, - max: lastRange.Max, - isMinInclusive: firstRange.IsMinInclusive, + return new Documents.Routing.Range( + min: firstRange.Min, + max: lastRange.Max, + isMinInclusive: firstRange.IsMinInclusive, isMaxInclusive: firstRange.IsMaxInclusive); } - /// - /// Validates whether all ranges in the list have consistent inclusivity for both `IsMinInclusive` and `IsMaxInclusive` boundaries. - /// This ensures that all ranges either have the same inclusivity or exclusivity for their minimum and maximum boundaries. - /// If there are any inconsistencies in the inclusivity/exclusivity of the ranges, it throws an `InvalidOperationException`. + /// + /// Validates whether all ranges in the list have consistent inclusivity for both `IsMinInclusive` and `IsMaxInclusive` boundaries. + /// This ensures that all ranges either have the same inclusivity or exclusivity for their minimum and maximum boundaries. + /// If there are any inconsistencies in the inclusivity/exclusivity of the ranges, it throws an `InvalidOperationException`. + /// + /// The logic works as follows: + /// - The method assumes that the `ranges` list is never null. + /// - It starts by checking the first range in the list to establish a baseline for comparison. + /// - It then iterates over the remaining ranges, comparing their `IsMinInclusive` and `IsMaxInclusive` values with those of the first range. + /// - If any range differs from the first in terms of inclusivity or exclusivity (either for the min or max boundary), the method sets a flag (`areAnyDifferent`) and exits the loop early. + /// - If any differences are found, the method gathers the distinct `IsMinInclusive` and `IsMaxInclusive` values found across all ranges. + /// - It then throws an `InvalidOperationException`, including the distinct values in the exception message to indicate the specific inconsistencies. + /// + /// This method is useful in scenarios where the ranges need to have uniform inclusivity for boundary conditions. + /// + /// The list of ranges to validate. Each range has `IsMinInclusive` and `IsMaxInclusive` values that represent the inclusivity of its boundaries. + /// + /// Thrown when `IsMinInclusive` or `IsMaxInclusive` values are inconsistent across ranges. The exception message includes details of the inconsistencies. + /// + /// + /// > ranges = new List> + /// { + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, + /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, + /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } + /// }; /// - /// The logic works as follows: - /// - The method assumes that the `ranges` list is never null. - /// - It starts by checking the first range in the list to establish a baseline for comparison. - /// - It then iterates over the remaining ranges, comparing their `IsMinInclusive` and `IsMaxInclusive` values with those of the first range. - /// - If any range differs from the first in terms of inclusivity or exclusivity (either for the min or max boundary), the method sets a flag (`areAnyDifferent`) and exits the loop early. - /// - If any differences are found, the method gathers the distinct `IsMinInclusive` and `IsMaxInclusive` values found across all ranges. - /// - It then throws an `InvalidOperationException`, including the distinct values in the exception message to indicate the specific inconsistencies. - /// - /// This method is useful in scenarios where the ranges need to have uniform inclusivity for boundary conditions. - /// - /// The list of ranges to validate. Each range has `IsMinInclusive` and `IsMaxInclusive` values that represent the inclusivity of its boundaries. - /// - /// Thrown when `IsMinInclusive` or `IsMaxInclusive` values are inconsistent across ranges. The exception message includes details of the inconsistencies. - /// - /// - /// > ranges = new List> - /// { - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = true }, - /// new Documents.Routing.Range { IsMinInclusive = true, IsMaxInclusive = false }, - /// new Documents.Routing.Range { IsMinInclusive = false, IsMaxInclusive = false } - /// }; - /// /// EnsureConsistentInclusivity(ranges); - /// - /// // This will throw an InvalidOperationException because there are different inclusivity values for IsMinInclusive and IsMaxInclusive. - /// ]]> + /// + /// // This will throw an InvalidOperationException because there are different inclusivity values for IsMinInclusive and IsMaxInclusive. + /// ]]> /// internal static void EnsureConsistentInclusivity(List> ranges) { @@ -1474,68 +1474,68 @@ internal static void EnsureConsistentInclusivity(List - /// Feature: Feed Range Subset Verification - /// - /// Determines whether the specified child range is entirely within the bounds of the parent range. - /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. - /// - /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, - /// taking into account whether each boundary is inclusive or exclusive. - /// - /// - For the `Max` boundary: - /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. - /// - If the parent range's max is inclusive and the child range's max is exclusive, this combination is not supported and a is thrown. - /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. - /// - This applies to the following combinations: - /// - (false, true): Parent max is exclusive, child max is inclusive. - /// - (true, true): Both max values are inclusive. - /// - (false, false): Both max values are exclusive. - /// - (true, false): Parent max is inclusive, child max is exclusive. - /// - **NotSupportedException Scenario:** This case is not supported because handling a scenario where the parent range has an inclusive maximum and the child range has an exclusive maximum requires additional logic that is not implemented. - /// - If encountered, a is thrown with a message explaining that this combination is not supported. - /// - /// - For the `Min` boundary: - /// - It checks whether the parent range contains the child range's min value, regardless of inclusivity. - /// - /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. - /// - /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: - /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: - /// - The parent range is exclusive at max, but the child range is inclusive. This is supported and will check if the parent contains the child's max. - /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: - /// - Both ranges are exclusive at max. This is supported and will check if the parent contains the child's max. - /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: - /// - Both ranges are inclusive at max. This is supported and will check if the max values are equal or if the parent contains the child's max. - /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: - /// - The parent range is inclusive at max, but the child range is exclusive. This combination is not supported and will result in a being thrown. - /// - /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. - /// - /// Additionally, the method performs null checks on the parameters: - /// - If is null, an is thrown. - /// - If is null, an is thrown. - /// - /// - /// Thrown when or is null. - /// - /// - /// Thrown when is inclusive at max and is exclusive at max. - /// This combination is not supported and requires specific handling. - /// - /// - /// - /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); - /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). - /// ]]> - /// - /// - /// Returns true if the child range is a subset of the parent range, meaning the child range's - /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// + /// Feature: Feed Range Subset Verification + /// + /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. + /// + /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, + /// taking into account whether each boundary is inclusive or exclusive. + /// + /// - For the `Max` boundary: + /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. + /// - If the parent range's max is inclusive and the child range's max is exclusive, this combination is not supported and a is thrown. + /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. + /// - This applies to the following combinations: + /// - (false, true): Parent max is exclusive, child max is inclusive. + /// - (true, true): Both max values are inclusive. + /// - (false, false): Both max values are exclusive. + /// - (true, false): Parent max is inclusive, child max is exclusive. + /// - **NotSupportedException Scenario:** This case is not supported because handling a scenario where the parent range has an inclusive maximum and the child range has an exclusive maximum requires additional logic that is not implemented. + /// - If encountered, a is thrown with a message explaining that this combination is not supported. + /// + /// - For the `Min` boundary: + /// - It checks whether the parent range contains the child range's min value, regardless of inclusivity. + /// + /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. + /// + /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: + /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: + /// - The parent range is exclusive at max, but the child range is inclusive. This is supported and will check if the parent contains the child's max. + /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: + /// - Both ranges are exclusive at max. This is supported and will check if the parent contains the child's max. + /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: + /// - Both ranges are inclusive at max. This is supported and will check if the max values are equal or if the parent contains the child's max. + /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: + /// - The parent range is inclusive at max, but the child range is exclusive. This combination is not supported and will result in a being thrown. + /// + /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. + /// + /// Additionally, the method performs null checks on the parameters: + /// - If is null, an is thrown. + /// - If is null, an is thrown. + /// + /// + /// Thrown when or is null. + /// + /// + /// Thrown when is inclusive at max and is exclusive at max. + /// This combination is not supported and requires specific handling. + /// + /// + /// + /// parentRange = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// + /// bool isSubset = IsSubset(parentRange, childRange); + /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// ]]> + /// + /// + /// Returns true if the child range is a subset of the parent range, meaning the child range's + /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. /// internal static bool IsSubset( Documents.Routing.Range parentRange, @@ -1554,7 +1554,8 @@ internal static bool IsSubset( bool isMaxWithinParent = (parentRange.IsMaxInclusive, childRange.IsMaxInclusive) switch { (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive - (true, false) => throw new NotSupportedException("The combination where the parent range's maximum is inclusive and the child range's maximum is exclusive is not supported in the current implementation. This case needs specific handling, which has not been implemented."), + (true, false) => throw new NotSupportedException("The combination where the parent range's maximum is inclusive and the child range's maximum is exclusive is not supported in the current implementation."), + _ => ContainerCore.IsChildMaxWithinParent(parentRange, childRange.Max) // Default for the following combinations: // (true, true): Both max values are inclusive // (false, false): Both max values are exclusive @@ -1577,5 +1578,5 @@ private static bool IsChildMaxWithinParent( { return parentRange.Max == childRangeMax || parentRange.Contains(childRangeMax); } - } -} + } +} From 10d6c9adf530971985873ed170e1c0ff671999b0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 2 Oct 2024 14:27:29 -0400 Subject: [PATCH 141/145] update docs --- .../src/Resource/Container/Container.cs | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 81b2335653..97e3b64564 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1784,31 +1784,30 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV ChangeFeedHandler> onChangesDelegate); /// - /// Determines whether the given child feed range is a part of the specified parent feed range. - /// This method performs a comparison between the effective ranges of the child and parent feed ranges, determining if the child is fully contained within the parent. - /// - /// The feed range representing the parent range. - /// The feed range representing the child range. - /// A token to cancel the operation if needed. - /// - /// - /// - /// - /// - /// True if the child feed range is a subset of the parent feed range; otherwise, false. + /// Determines whether the given y feed range is a part of the specified x feed range. + /// + /// The feed range representing the x range. + /// The feed range representing the y range. + /// A token to cancel the operation if needed. + /// + /// + /// + /// + /// + /// True if the y feed range is a part of the x feed range; otherwise, false. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange x, Cosmos.FeedRange y, From 4ebbad9992cc75f3432f4212bce8677d790daa60 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 3 Oct 2024 09:26:05 -0400 Subject: [PATCH 142/145] change parent to x, and child to y, everywhere. --- .../src/EncryptionContainer.cs | 8 +- .../src/EncryptionContainer.cs | 4 +- .../src/Resource/Container/Container.cs | 4 + .../Resource/Container/ContainerCore.Items.cs | 200 +++--- .../Resource/Container/ContainerInlineCore.cs | 8 +- .../Resource/Container/ContainerInternal.cs | 4 +- .../IsFeedRangePartOfAsyncTests.cs | 656 +++++++++--------- 7 files changed, 443 insertions(+), 441 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index d5510c2097..470c86b387 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -1031,13 +1031,13 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV #if SDKPROJECTREF public override Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, + Cosmos.FeedRange x, + Cosmos.FeedRange y, CancellationToken cancellationToken = default) { return this.container.IsFeedRangePartOfAsync( - parentFeedRange, - childFeedRange, + x, + y, cancellationToken); } #endif diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index ae70af6718..ab83f9e04d 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -764,8 +764,8 @@ public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV } public override Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, + Cosmos.FeedRange x, + Cosmos.FeedRange y, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 1439c8a025..434a49ebbe 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1808,6 +1808,10 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the y feed range is a part of the x feed range; otherwise, false. + /// The broader feed range representing the larger, encompassing logical partition. + /// The smaller, more granular feed range that needs to be checked for containment within the broader feed range. + /// An optional cancellation token to cancel the operation before completion. + /// Returns a boolean indicating whether the y feed range is fully contained within the x feed range. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange x, Cosmos.FeedRange y, diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index bd77817445..127a641862 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1259,13 +1259,13 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( } /// - /// This method is useful for determining if a smaller, more granular feed range (child) is fully contained within a broader feed range (parent), which is a common operation in distributed systems to manage partitioned data. + /// This method is useful for determining if a smaller, more granular feed range (y) is fully contained within a broader feed range (x), which is a common operation in distributed systems to manage partitioned data. /// - /// - **Parent and Child Feed Ranges**: Both `parentFeedRange` and `childFeedRange` are representations of logical partitions or ranges within the Cosmos DB container. + /// - **x and y Feed Ranges**: Both `x` and `y` are representations of logical partitions or ranges within the Cosmos DB container. /// - These ranges are typically used for operations such as querying or reading data within a specified range of partition key values. /// /// - **Validation and Parsing**: - /// - The method begins by validating that neither `parentFeedRange` nor `childFeedRange` is null. If either is null, an `ArgumentNullException` is thrown. + /// - The method begins by validating that neither `x` nor `y` is null. If either is null, an `ArgumentNullException` is thrown. /// - It then checks whether each feed range is of type `FeedRangeInternal`. If not, it attempts to parse the JSON representation of the feed range into the internal format (`FeedRangeInternal`). /// - If the parsing fails, an `ArgumentException` is thrown, indicating that the feed range is of an unknown or unsupported format. /// @@ -1278,49 +1278,38 @@ private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( /// - These effective ranges are returned as lists of `Range`, which represent the partition key boundaries. /// /// - **Inclusivity Consistency**: - /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the parent and child feed ranges. + /// - Before performing the subset comparison, the method checks that the inclusivity of the boundary conditions (`IsMinInclusive` and `IsMaxInclusive`) is consistent across all ranges in both the x and y feed ranges. /// - This ensures that the comparison between ranges is logically correct and avoids potential mismatches due to differing boundary conditions. /// /// - **Subset Check**: - /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the child feed range is fully contained within the merged effective range of the parent feed range. + /// - Finally, the method calls `ContainerCore.IsSubset`, which checks if the merged effective range of the y feed range is fully contained within the merged effective range of the x feed range. /// - Merging the ranges ensures that the comparison accounts for multiple ranges and considers the full span of each feed range. /// /// - **Exception Handling**: /// - Any exceptions related to document client errors are caught, and a `CosmosException` is thrown, wrapping the original `DocumentClientException`. /// + /// The broader feed range representing the larger, encompassing logical partition. + /// The smaller, more granular feed range that needs to be checked for containment within the broader feed range. + /// An optional cancellation token to cancel the operation before completion. + /// Returns a boolean indicating whether the y feed range is fully contained within the x feed range. public override async Task IsFeedRangePartOfAsync( - FeedRange parentFeedRange, - FeedRange childFeedRange, + FeedRange x, + FeedRange y, CancellationToken cancellationToken = default) { using (ITrace trace = Tracing.Trace.GetRootTrace("ContainerCore FeedRange IsFeedRangePartOfAsync Async", TraceComponent.Unknown, Tracing.TraceLevel.Info)) { - if (parentFeedRange == null || childFeedRange == null) + if (x == null || y == null) { - throw new ArgumentNullException(parentFeedRange == null - ? nameof(parentFeedRange) - : nameof(childFeedRange), $"Argument cannot be null."); + throw new ArgumentNullException(x == null + ? nameof(x) + : nameof(y), $"Argument cannot be null."); } try { - if (parentFeedRange is not FeedRangeInternal parentFeedRangeInternal) - { - if (!FeedRangeInternal.TryParse(parentFeedRange.ToJsonString(), out parentFeedRangeInternal)) - { - throw new ArgumentException( - string.Format("The provided string, '{0}', for '{1}', does not represent any known format.", parentFeedRange.ToJsonString(), nameof(parentFeedRange))); - } - } - - if (childFeedRange is not FeedRangeInternal childFeedRangeInternal) - { - if (!FeedRangeInternal.TryParse(childFeedRange.ToJsonString(), out childFeedRangeInternal)) - { - throw new ArgumentException( - string.Format("The provided string, '{0}', for '{1}', does not represent any known format.", childFeedRange.ToJsonString(), nameof(childFeedRange))); - } - } + FeedRangeInternal xFeedRangeInternal = ContainerCore.ConvertToFeedRangeInternal(x, nameof(x)); + FeedRangeInternal yFeedRangeInternal = ContainerCore.ConvertToFeedRangeInternal(y, nameof(y)); PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken); @@ -1331,24 +1320,24 @@ public override async Task IsFeedRangePartOfAsync( IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); - List> parentEffectiveRanges = await parentFeedRangeInternal.GetEffectiveRangesAsync( + List> xEffectiveRanges = await xFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace); - List> childEffectiveRanges = await childFeedRangeInternal.GetEffectiveRangesAsync( + List> yEffectiveRanges = await yFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace); - ContainerCore.EnsureConsistentInclusivity(parentEffectiveRanges); - ContainerCore.EnsureConsistentInclusivity(childEffectiveRanges); + ContainerCore.EnsureConsistentInclusivity(xEffectiveRanges); + ContainerCore.EnsureConsistentInclusivity(yEffectiveRanges); return ContainerCore.IsSubset( - parentRange: ContainerCore.MergeRanges(parentEffectiveRanges), - childRange: ContainerCore.MergeRanges(childEffectiveRanges)); + ContainerCore.MergeRanges(xEffectiveRanges), + ContainerCore.MergeRanges(yEffectiveRanges)); } catch (DocumentClientException dce) { @@ -1357,6 +1346,30 @@ public override async Task IsFeedRangePartOfAsync( } } + /// + /// Converts a given feed range to its internal representation (FeedRangeInternal). + /// If the provided feed range is already of type FeedRangeInternal, it returns it directly. + /// Otherwise, it attempts to parse the feed range into a FeedRangeInternal. + /// If parsing fails, an is thrown. + /// + /// The feed range to be converted into an internal representation. + /// The name of the parameter being converted, used for exception messages. + /// The converted FeedRangeInternal object. + /// Thrown when the provided feed range cannot be parsed into a known format. + private static FeedRangeInternal ConvertToFeedRangeInternal(FeedRange feedRange, string paramName) + { + if (feedRange is not FeedRangeInternal feedRangeInternal) + { + if (!FeedRangeInternal.TryParse(feedRange.ToJsonString(), out feedRangeInternal)) + { + throw new ArgumentException( + string.Format("The provided string, '{0}', for '{1}', does not represent any known format.", feedRange.ToJsonString(), paramName)); + } + } + + return feedRangeInternal; + } + /// /// Merges a list of feed ranges into a single range by taking the minimum value of the first range and the maximum value of the last range. /// This function ensures that the resulting range covers the entire span of the input ranges. @@ -1384,19 +1397,6 @@ public override async Task IsFeedRangePartOfAsync( /// /// Thrown when the list of ranges is empty. /// - /// - /// > ranges = new List> - /// { - /// new Documents.Routing.Range("A", "C", true, false), - /// new Documents.Routing.Range("D", "F", true, true), - /// new Documents.Routing.Range("G", "I", false, true), - /// }; - /// - /// Documents.Routing.Range mergedRange = MergeRanges(ranges); - /// // The merged range would span from "A" to "I", taking the minimum of the first range and the maximum of the last. - /// ]]> - /// private static Documents.Routing.Range MergeRanges( List> ranges) { @@ -1475,108 +1475,106 @@ internal static void EnsureConsistentInclusivity(List - /// Feature: Feed Range Subset Verification - /// - /// Determines whether the specified child range is entirely within the bounds of the parent range. + /// Determines whether the specified y range is entirely within the bounds of the x range. /// This includes checking both the minimum and maximum boundaries of the ranges for inclusion. /// - /// The method checks whether the `Min` and `Max` boundaries of `childRange` are within `parentRange`, + /// The method checks whether the `Min` and `Max` boundaries of `y` are within `x`, /// taking into account whether each boundary is inclusive or exclusive. /// /// - For the `Max` boundary: - /// - If the parent range's max is exclusive and the child range's max is inclusive, it checks whether the parent range contains the child range's max value. - /// - If the parent range's max is inclusive and the child range's max is exclusive, this combination is not supported and a is thrown. - /// - For all other cases, it checks if the max values are equal or whether the parent range contains the child range's max. + /// - If the x range's max is exclusive and the y range's max is inclusive, it checks whether the x range contains the y range's max value. + /// - If the x range's max is inclusive and the y range's max is exclusive, this combination is not supported and a is thrown. + /// - For all other cases, it checks if the max values are equal or whether the x range contains the y range's max. /// - This applies to the following combinations: - /// - (false, true): Parent max is exclusive, child max is inclusive. + /// - (false, true): x max is exclusive, y max is inclusive. /// - (true, true): Both max values are inclusive. /// - (false, false): Both max values are exclusive. - /// - (true, false): Parent max is inclusive, child max is exclusive. - /// - **NotSupportedException Scenario:** This case is not supported because handling a scenario where the parent range has an inclusive maximum and the child range has an exclusive maximum requires additional logic that is not implemented. + /// - (true, false): x max is inclusive, y max is exclusive. + /// - **NotSupportedException Scenario:** This case is not supported because handling a scenario where the x range has an inclusive maximum and the y range has an exclusive maximum requires additional logic that is not implemented. /// - If encountered, a is thrown with a message explaining that this combination is not supported. /// /// - For the `Min` boundary: - /// - It checks whether the parent range contains the child range's min value, regardless of inclusivity. + /// - It checks whether the x range contains the y range's min value, regardless of inclusivity. /// - /// The method ensures the child range is considered a subset only if both its min and max values fall within the parent range. + /// The method ensures the y range is considered a subset only if both its min and max values fall within the x range. /// - /// Summary of combinations for `parentRange.IsMaxInclusive` and `childRange.IsMaxInclusive`: - /// 1. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == true: - /// - The parent range is exclusive at max, but the child range is inclusive. This is supported and will check if the parent contains the child's max. - /// 2. parentRange.IsMaxInclusive == false, childRange.IsMaxInclusive == false: - /// - Both ranges are exclusive at max. This is supported and will check if the parent contains the child's max. - /// 3. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == true: - /// - Both ranges are inclusive at max. This is supported and will check if the max values are equal or if the parent contains the child's max. - /// 4. parentRange.IsMaxInclusive == true, childRange.IsMaxInclusive == false: - /// - The parent range is inclusive at max, but the child range is exclusive. This combination is not supported and will result in a being thrown. + /// Summary of combinations for `x.IsMaxInclusive` and `y.IsMaxInclusive`: + /// 1. x.IsMaxInclusive == false, y.IsMaxInclusive == true: + /// - The x range is exclusive at max, but the y range is inclusive. This is supported and will check if the x contains the y's max. + /// 2. x.IsMaxInclusive == false, y.IsMaxInclusive == false: + /// - Both ranges are exclusive at max. This is supported and will check if the x contains the y's max. + /// 3. x.IsMaxInclusive == true, y.IsMaxInclusive == true: + /// - Both ranges are inclusive at max. This is supported and will check if the max values are equal or if the x contains the y's max. + /// 4. x.IsMaxInclusive == true, y.IsMaxInclusive == false: + /// - The x range is inclusive at max, but the y range is exclusive. This combination is not supported and will result in a being thrown. /// - /// The method returns true only if both the min and max boundaries of the child range are within the parent range's boundaries. + /// The method returns true only if both the min and max boundaries of the y range are within the x range's boundaries. /// /// Additionally, the method performs null checks on the parameters: - /// - If is null, an is thrown. - /// - If is null, an is thrown. + /// - If is null, an is thrown. + /// - If is null, an is thrown. /// /// - /// Thrown when or is null. + /// Thrown when or is null. /// /// - /// Thrown when is inclusive at max and is exclusive at max. + /// Thrown when is inclusive at max and is exclusive at max. /// This combination is not supported and requires specific handling. /// /// /// /// parentRange = new Documents.Routing.Range("A", "Z", true, true); - /// Documents.Routing.Range childRange = new Documents.Routing.Range("B", "Y", true, true); + /// Documents.Routing.Range x = new Documents.Routing.Range("A", "Z", true, true); + /// Documents.Routing.Range y = new Documents.Routing.Range("B", "Y", true, true); /// - /// bool isSubset = IsSubset(parentRange, childRange); - /// isSubset will be true because the child range (B-Y) is fully contained within the parent range (A-Z). + /// bool isSubset = IsSubset(x, y); + /// isSubset will be true because the y range (B-Y) is fully contained within the x range (A-Z). /// ]]> /// /// - /// Returns true if the child range is a subset of the parent range, meaning the child range's - /// minimum and maximum values fall within the bounds of the parent range. Returns false otherwise. + /// Returns true if the y range is a subset of the x range, meaning the y range's + /// minimum and maximum values fall within the bounds of the x range. Returns false otherwise. /// internal static bool IsSubset( - Documents.Routing.Range parentRange, - Documents.Routing.Range childRange) + Documents.Routing.Range x, + Documents.Routing.Range y) { - if (parentRange is null) + if (x is null) { - throw new ArgumentNullException(nameof(parentRange)); + throw new ArgumentNullException(nameof(x)); } - if (childRange is null) + if (y is null) { - throw new ArgumentNullException(nameof(childRange)); + throw new ArgumentNullException(nameof(y)); } - bool isMaxWithinParent = (parentRange.IsMaxInclusive, childRange.IsMaxInclusive) switch + bool isMaxWithinX = (x.IsMaxInclusive, y.IsMaxInclusive) switch { - (false, true) => parentRange.Contains(childRange.Max), // Parent max is exclusive, child max is inclusive - (true, false) => throw new NotSupportedException("The combination where the parent range's maximum is inclusive and the child range's maximum is exclusive is not supported in the current implementation."), + (false, true) => x.Contains(y.Max), // x max is exclusive, y max is inclusive + (true, false) => throw new NotSupportedException("The combination where the x range's maximum is inclusive and the y range's maximum is exclusive is not supported in the current implementation."), - _ => ContainerCore.IsChildMaxWithinParent(parentRange, childRange.Max) // Default for the following combinations: - // (true, true): Both max values are inclusive - // (false, false): Both max values are exclusive + _ => ContainerCore.IsYMaxWithinX(x, y) // Default for the following combinations: + // (true, true): Both max values are inclusive + // (false, false): Both max values are exclusive }; - bool isMinWithinParent = parentRange.Contains(childRange.Min); + bool isMinWithinX = x.Contains(y.Min); - return isMinWithinParent && isMaxWithinParent; + return isMinWithinX && isMaxWithinX; } /// - /// Determines whether the given maximum value of the child range is either equal to or contained within the parent range. + /// Determines whether the given maximum value of the y range is either equal to or contained within the x range. /// - /// The parent range to compare against, which defines the boundary. - /// The maximum value of the child range to be checked. - /// True if the maximum value of the child range is equal to or contained within the parent range; otherwise, false. - private static bool IsChildMaxWithinParent( - Documents.Routing.Range parentRange, - string childRangeMax) + /// The x range to compare against, which defines the boundary. + /// The y range to be checked. + /// True if the maximum value of the y range is equal to or contained within the x range; otherwise, false. + private static bool IsYMaxWithinX( + Documents.Routing.Range x, + Documents.Routing.Range y) { - return parentRange.Max == childRangeMax || parentRange.Contains(childRangeMax); + return x.Max == y.Max || x.Contains(y.Max); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 044288fbd3..11a3b866c4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -679,8 +679,8 @@ public override Task DeleteAllItemsByPartitionKeyStreamAsync( } public override Task IsFeedRangePartOfAsync( - FeedRange parentFeedRange, - FeedRange childFeedRange, + FeedRange x, + FeedRange y, CancellationToken cancellationToken = default) { // TODO: The current use of Documents.OperationType.ReadFeed is not a precise fit for this operation. @@ -693,8 +693,8 @@ public override Task IsFeedRangePartOfAsync( operationType: Documents.OperationType.ReadFeed, requestOptions: null, task: (trace) => base.IsFeedRangePartOfAsync( - parentFeedRange: parentFeedRange, - childFeedRange: childFeedRange, + x, + y, cancellationToken: cancellationToken)); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index acb85f95a3..84fb20480c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -153,8 +153,8 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV ChangeFeedHandler> onChangesDelegate); public abstract Task IsFeedRangePartOfAsync( - Cosmos.FeedRange parentFeedRange, - Cosmos.FeedRange childFeedRange, + Cosmos.FeedRange x, + Cosmos.FeedRange y, CancellationToken cancellationToken = default); #endif diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs index 8519e7454c..b867c764e1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/IsFeedRangePartOfAsyncTests.cs @@ -79,26 +79,26 @@ private async static Task CreateHierarchicalPartitionContaine /// /// - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// Indicates whether the child partition key is expected to be part of the parent feed range (true if it is, false if it is not). + /// The starting value of the x feed range. + /// The ending value of the x feed range. + /// Indicates whether the y partition key is expected to be part of the x feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset")] [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range is subset using V2 hash testContext")] [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF is not subset")] - [Description("Validate if the child partition key is part of the parent feed range using either V1 or V2 PartitionKeyDefinitionVersion.")] - public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( - string parentMinimum, - string parentMaximum, + [Description("Validate if the y partition key is part of the x feed range using either V1 or V2 PartitionKeyDefinitionVersion.")] + public async Task GivenFeedRangeYPartitionKeyIsPartOfXFeedRange( + string xMinimum, + string xMaximum, bool expectedIsFeedRangePartOfAsync) { try @@ -120,7 +120,7 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + new FeedRangeEpk(new Documents.Routing.Range(xMinimum, xMaximum, true, false)), feedRange, cancellationToken: CancellationToken.None); @@ -155,24 +155,24 @@ public async Task GivenFeedRangeChildPartitionKeyIsPartOfParentFeedRange( /// /// - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// A boolean value indicating whether the child hierarchical partition key is expected to be part of the parent feed range (true if it is, false if it is not). + /// The starting value of the x feed range. + /// The ending value of the x feed range. + /// A boolean value indicating whether the y hierarchical partition key is expected to be part of the x feed range (true if it is, false if it is not). [TestMethod] [Owner("philipthomas-MSFT")] [DataRow("", "FFFFFFFFFFFFFFFF", true, DisplayName = "Full range")] [DataRow("3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, DisplayName = "Made-up range 3FFFFFFFFFFFFFFF-7FFFFFFFFFFFFFFF")] - [Description("Validate if the child hierarchical partition key is part of the parent feed range.")] - public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedRange( - string parentMinimum, - string parentMaximum, + [Description("Validate if the y hierarchical partition key is part of the x feed range.")] + public async Task GivenFeedRangeYHierarchicalPartitionKeyIsPartOfXFeedRange( + string xMinimum, + string xMaximum, bool expectedIsFeedRangePartOfAsync) { try @@ -197,7 +197,7 @@ public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedR this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, false)), + new FeedRangeEpk(new Documents.Routing.Range(xMinimum, xMaximum, true, false)), feedRange, cancellationToken: CancellationToken.None); @@ -232,20 +232,20 @@ public async Task GivenFeedRangeChildHierarchicalPartitionKeyIsPartOfParentFeedR /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeIsNull() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenYFeedRangeIsNull() { FeedRange feedRange = default; - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidYFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"Argument cannot be null."); } @@ -254,20 +254,20 @@ await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAs /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenChildFeedRangeHasNoJson() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenYFeedRangeHasNoJson() { FeedRange feedRange = Mock.Of(); - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidYFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"Value cannot be null. (Parameter 'value')"); } @@ -276,27 +276,27 @@ await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAs /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentExceptionWhenChildFeedRangeHasInvalidJson() + public async Task GivenFeedRangeThrowsArgumentExceptionWhenYFeedRangeHasInvalidJson() { Mock mockFeedRange = new Mock(MockBehavior.Strict); mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); FeedRange feedRange = mockFeedRange.Object; - await this.GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidYFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, - expectedMessage: $"The provided string, '', for 'childFeedRange', does not represent any known format."); + expectedMessage: $"The provided string, '', for 'y', does not represent any known format."); } - private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + private async Task GivenInvalidYFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( FeedRange feedRange, string expectedMessage) where TExceeption : Exception @@ -360,20 +360,20 @@ private async Task GivenInvalidChildFeedRangeExpectsArgumentExceptionIsFeedRange /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeIsNull() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenXFeedRangeIsNull() { FeedRange feedRange = default; - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidXFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"Argument cannot be null."); } @@ -382,20 +382,20 @@ await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfA /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenParentFeedRangeHasNoJson() + public async Task GivenFeedRangeThrowsArgumentNullExceptionWhenXFeedRangeHasNoJson() { FeedRange feedRange = Mock.Of(); - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidXFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, expectedMessage: $"Value cannot be null. (Parameter 'value')"); } @@ -404,27 +404,27 @@ await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfA /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentExceptionWhenParentFeedRangeHasInvalidJson() + public async Task GivenFeedRangeThrowsArgumentExceptionWhenXFeedRangeHasInvalidJson() { Mock mockFeedRange = new Mock(MockBehavior.Strict); mockFeedRange.Setup(feedRange => feedRange.ToJsonString()).Returns(""); FeedRange feedRange = mockFeedRange.Object; - await this.GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + await this.GivenInvalidXFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( feedRange: feedRange, - expectedMessage: $"The provided string, '', for 'parentFeedRange', does not represent any known format."); + expectedMessage: $"The provided string, '', for 'x', does not represent any known format."); } - private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( + private async Task GivenInvalidXFeedRangeExpectsArgumentExceptionIsFeedRangePartOfAsyncTestAsync( FeedRange feedRange, string expectedMessage) where TException : Exception @@ -488,29 +488,29 @@ private async Task GivenInvalidParentFeedRangeExpectsArgumentExceptionIsFeedRang /// /// - /// The starting value of the child feed range. - /// The ending value of the child feed range. - /// Specifies whether the maximum value of the child feed range is inclusive. - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// Specifies whether the maximum value of the parent feed range is inclusive. + /// The starting value of the y feed range. + /// The ending value of the y feed range. + /// Specifies whether the maximum value of the y feed range is inclusive. + /// The starting value of the x feed range. + /// The ending value of the x feed range. + /// Specifies whether the maximum value of the x feed range is inclusive. [TestMethod] [Owner("philipthomas-MSFT")] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeThrowsNotSupportedExceptionWhenChildIsMaxExclusiveAndParentIsMaxInclusive), DynamicDataSourceType.Method)] - public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseNotSupportedExceptionTestAsync( - string childMinimum, - string childMaximum, - bool childIsMaxInclusive, - string parentMinimum, - string parentMaximum, - bool parentIsMaxInclusive) + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeThrowsNotSupportedExceptionWhenYIsMaxExclusiveAndXIsMaxInclusive), DynamicDataSourceType.Method)] + public async Task GivenFeedRangeYPartOfOrNotPartOfXWhenBothIsMaxInclusiveCanBeTrueOrFalseNotSupportedExceptionTestAsync( + string yMinimum, + string yMaximum, + bool yIsMaxInclusive, + string xMinimum, + string xMaximum, + bool xIsMaxInclusive) { try { @@ -530,8 +530,8 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi NotSupportedException exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container.IsFeedRangePartOfAsync( - new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(xMinimum, xMaximum, true, xIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(yMinimum, yMaximum, true, yIsMaxInclusive)), cancellationToken: CancellationToken.None)); if (exception == null) @@ -559,35 +559,35 @@ await containerContext.Container.IsFeedRangePartOfAsync( /// /// - /// The starting value of the child feed range. - /// The ending value of the child feed range. - /// Specifies whether the maximum value of the child feed range is inclusive. - /// The starting value of the parent feed range. - /// The ending value of the parent feed range. - /// Specifies whether the maximum value of the parent feed range is inclusive. - /// Indicates whether the child feed range is expected to be a subset of the parent feed range. + /// The starting value of the y feed range. + /// The ending value of the y feed range. + /// Specifies whether the maximum value of the y feed range is inclusive. + /// The starting value of the x feed range. + /// The ending value of the x feed range. + /// Specifies whether the maximum value of the x feed range is inclusive. + /// Indicates whether the y feed range is expected to be a subset of the x feed range. [TestMethod] [Owner("philipthomas-MSFT")] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] - [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse), DynamicDataSourceType.Method)] - public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( - string childMinimum, - string childMaximum, - bool childIsMaxInclusive, - string parentMinimum, - string parentMaximum, - bool parentIsMaxInclusive, + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYPartOfXWhenBothYAndXIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYNotPartOfXWhenBothYAndXIsMaxInclusiveTrue), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYNotPartOfXWhenBothIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYNotPartOfXWhenYAndXIsMaxInclusiveAreFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYPartOfXWhenYIsMaxInclusiveTrueAndXIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + [DynamicData(nameof(IsFeedRangePartOfAsyncTests.FeedRangeYNotPartOfXWhenYIsMaxInclusiveTrueAndXIsMaxInclusiveFalse), DynamicDataSourceType.Method)] + public async Task GivenFeedRangeYPartOfOrNotPartOfXWhenBothIsMaxInclusiveCanBeTrueOrFalseTestAsync( + string yMinimum, + string yMaximum, + bool yIsMaxInclusive, + string xMinimum, + string xMaximum, + bool xIsMaxInclusive, bool expectedIsFeedRangePartOfAsync) { try @@ -606,8 +606,8 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi this.TestContext.LogTestExecutionForContainer(containerContext); bool actualIsFeedRangePartOfAsync = await containerContext.Container.IsFeedRangePartOfAsync( - new FeedRangeEpk(new Documents.Routing.Range(parentMinimum, parentMaximum, true, parentIsMaxInclusive)), - new FeedRangeEpk(new Documents.Routing.Range(childMinimum, childMaximum, true, childIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(xMinimum, xMaximum, true, xIsMaxInclusive)), + new FeedRangeEpk(new Documents.Routing.Range(yMinimum, yMaximum, true, yIsMaxInclusive)), cancellationToken: CancellationToken.None); if (expectedIsFeedRangePartOfAsync != actualIsFeedRangePartOfAsync) @@ -641,112 +641,112 @@ public async Task GivenFeedRangeChildPartOfOrNotPartOfParentWhenBothIsMaxInclusi /// /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenBothIsMaxInclusiveAreFalse() + private static IEnumerable FeedRangeYNotPartOfXWhenBothIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, does fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to just before 6666666666666666, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to just before 7333333333333333, fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, fits entirely within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, from 3FFFFFFFFFFFFFFF to just before 7FFFFFFFFFFFFFFF, fits entirely within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, from 7FFFFFFFFFFFFFFF to just before BFFFFFFFFFFFFFFF, fits entirely within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, from BFFFFFFFFFFFFFFF to just before FFFFFFFFFFFFFFFF, does fit within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 3FFFFFFFFFFFFFFF to just before 4CCCCCCCCCCCCCCC, fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 4CCCCCCCCCCCCCCC to just before 5999999999999999, fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 5999999999999999 to just before 6666666666666666, fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 6666666666666666 to just before 7333333333333333, fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 7333333333333333 to just before 7FFFFFFFFFFFFFFF, does fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", false, true }; // The y range, starting from a lower bound minimum and ending just before 3FFFFFFFFFFFFFFF, does not fit within the x range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. } /// /// /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildAndParentIsMaxInclusiveAreFalse() + private static IEnumerable FeedRangeYNotPartOfXWhenYAndXIsMaxInclusiveAreFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3FFFFFFFFFFFFFFF, but is not part of the parent range from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF. - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 3333333333333333, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range from 3333333333333333 to just before 6666666666666666 is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range from 7333333333333333 to just before FFFFFFFFFFFFFFFF is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range ends just before 7333333333333333, but is not part of the parent range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range ends just before 3FFFFFFFFFFFFFFF, but is not part of the x range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The y range ends just before 3FFFFFFFFFFFFFFF, but is not part of the x range from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The y range ends just before 3FFFFFFFFFFFFFFF, but is not part of the x range from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range ends just before 3333333333333333, but is not part of the x range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range from 3333333333333333 to just before 6666666666666666 is not part of the x range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range from 7333333333333333 to just before FFFFFFFFFFFFFFFF is not part of the x range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range ends just before 7333333333333333, but is not part of the x range from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF. } /// /// /// - private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + private static IEnumerable FeedRangeYPartOfXWhenYIsMaxInclusiveTrueAndXIsMaxInclusiveFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits within the parent range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, false }; // "The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, false }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits within the x range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, true }; // The y range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits within the x range, starting from a lower bound minimum and ending just before FFFFFFFFFFFFFFFF. + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", false, false }; // "The y range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from a lower bound minimum and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", false, false }; // The y range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from a lower bound minimum and ends just before 3FFFFFFFFFFFFFFF. + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 5999999999999999 to 6666666666666666 (inclusive), fits within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, true }; // The y range, from 6666666666666666 to 7333333333333333 (inclusive), fits within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. } /// /// /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenChildIsMaxInclusiveTrueAndParentIsMaxInclusiveFalse() + private static IEnumerable FeedRangeYNotPartOfXWhenYIsMaxInclusiveTrueAndXIsMaxInclusiveFalse() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends just before BFFFFFFFFFFFFFFF. - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends just before FFFFFFFFFFFFFFFF. - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. - yield return new object[] { "AA", "AA", true, "", "AA", false, false }; // The child range, which starts and ends at AA (inclusive), does not fit within the parent range, which starts from a lower bound minimum and ends just before AA (non-inclusive), due to the parent's non-inclusive upper boundary. - yield return new object[] { "AA", "AA", true, "AA", "BB", false, true }; // The child range, which starts and ends at AA (inclusive), fits entirely within the parent range, which starts at AA and ends just before BB (non-inclusive), due to the child's inclusive boundary at AA. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 7FFFFFFFFFFFFFFF and ends just before BFFFFFFFFFFFFFFF. + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from BFFFFFFFFFFFFFFF and ends just before FFFFFFFFFFFFFFFF. + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, false }; // The y range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends just before 7FFFFFFFFFFFFFFF. + yield return new object[] { "AA", "AA", true, "", "AA", false, false }; // The y range, which starts and ends at AA (inclusive), does not fit within the x range, which starts from a lower bound minimum and ends just before AA (non-inclusive), due to the x's non-inclusive upper boundary. + yield return new object[] { "AA", "AA", true, "AA", "BB", false, true }; // The y range, which starts and ends at AA (inclusive), fits entirely within the x range, which starts at AA and ends just before BB (non-inclusive), due to the y's inclusive boundary at AA. } /// /// /// - private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInclusiveFalseAndParentIsMaxInclusiveTrue() + private static IEnumerable FeedRangeYPartOfXWhenYIsMaxInclusiveFalseAndXIsMaxInclusiveTrue() { yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // @@ -766,127 +766,127 @@ private static IEnumerable FeedRangeChildPartOfParentWhenChildIsMaxInc /// /// - private static IEnumerable FeedRangeThrowsNotSupportedExceptionWhenChildIsMaxExclusiveAndParentIsMaxInclusive() + private static IEnumerable FeedRangeThrowsNotSupportedExceptionWhenYIsMaxExclusiveAndXIsMaxInclusive() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF') - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF') - yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3333333333333333' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7333333333333333' to 'FFFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') - yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to '3FFFFFFFFFFFFFFF') - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '3FFFFFFFFFFFFFFF' to '4CCCCCCCCCCCCCCC' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '4CCCCCCCCCCCCCCC' to '5999999999999999' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '5999999999999999' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '6666666666666666' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '7333333333333333' to '7FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') - yield return new object[] { "10", "11", false, "10", "10", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: '10' to '11' vs '10' to '10') - yield return new object[] { "A", "B", false, "A", "A", true }; // NotSupportedException thrown for child max exclusive and parent max inclusive (Range: 'A' to 'B' vs 'A' to 'A') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "", "3333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "3333333333333333", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '3333333333333333' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '7333333333333333' to 'FFFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '7FFFFFFFFFFFFFFF' to 'BFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", false, "", "FFFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: 'BFFFFFFFFFFFFFFF' to 'FFFFFFFFFFFFFFFF' vs '' to 'FFFFFFFFFFFFFFFF') + yield return new object[] { "", "3FFFFFFFFFFFFFFF", false, "", "3FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '' to '3FFFFFFFFFFFFFFF' vs '' to '3FFFFFFFFFFFFFFF') + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '3FFFFFFFFFFFFFFF' to '4CCCCCCCCCCCCCCC' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '4CCCCCCCCCCCCCCC' to '5999999999999999' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "5999999999999999", "6666666666666666", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '5999999999999999' to '6666666666666666' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "6666666666666666", "7333333333333333", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '6666666666666666' to '7333333333333333' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", false, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '7333333333333333' to '7FFFFFFFFFFFFFFF' vs '3FFFFFFFFFFFFFFF' to '7FFFFFFFFFFFFFFF') + yield return new object[] { "10", "11", false, "10", "10", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: '10' to '11' vs '10' to '10') + yield return new object[] { "A", "B", false, "A", "A", true }; // NotSupportedException thrown for y max exclusive and x max inclusive (Range: 'A' to 'B' vs 'A' to 'A') } /// /// /// - private static IEnumerable FeedRangeChildPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + private static IEnumerable FeedRangeYPartOfXWhenBothYAndXIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The child range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // The child range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 5999999999999999 to 6666666666666666 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 6666666666666666 to 7333333333333333 (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The child range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The y range, from 3FFFFFFFFFFFFFFF to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The y range, from 7FFFFFFFFFFFFFFF to BFFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, "", "FFFFFFFFFFFFFFFF", true, true }; // The y range, from BFFFFFFFFFFFFFFF to FFFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from a lower bound minimum and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "", "3FFFFFFFFFFFFFFF", true, true }; // The y range, from a lower bound minimum to 3FFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from a lower bound minimum and ends at 3FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3FFFFFFFFFFFFFFF", "4CCCCCCCCCCCCCCC", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The y range, from 3FFFFFFFFFFFFFFF to 4CCCCCCCCCCCCCCC (inclusive), fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "4CCCCCCCCCCCCCCC", "5999999999999999", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The y range, from 4CCCCCCCCCCCCCCC to 5999999999999999 (inclusive), fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "5999999999999999", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The y range, from 5999999999999999 to 6666666666666666 (inclusive), fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "6666666666666666", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The y range, from 6666666666666666 to 7333333333333333 (inclusive), fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "7FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, true }; // The y range, from 7333333333333333 to 7FFFFFFFFFFFFFFF (inclusive), fits entirely within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// /// /// - private static IEnumerable FeedRangeChildNotPartOfParentWhenBothChildAndParentIsMaxInclusiveTrue() + private static IEnumerable FeedRangeYNotPartOfXWhenBothYAndXIsMaxInclusiveTrue() { - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). - yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The child range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the parent range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "7FFFFFFFFFFFFFFF", "BFFFFFFFFFFFFFFF", true, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 7FFFFFFFFFFFFFFF and ends at BFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3FFFFFFFFFFFFFFF", true, "BFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", true, false }; // The y range, starting from a lower bound minimum and ending at 3FFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from BFFFFFFFFFFFFFFF and ends at FFFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "3333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The y range, starting from a lower bound minimum and ending at 3333333333333333 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "3333333333333333", "6666666666666666", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The y range, from 3333333333333333 to 6666666666666666 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "7333333333333333", "FFFFFFFFFFFFFFFF", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The y range, from 7333333333333333 to FFFFFFFFFFFFFFFF (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). + yield return new object[] { "", "7333333333333333", true, "3FFFFFFFFFFFFFFF", "7FFFFFFFFFFFFFFF", true, false }; // The y range, starting from a lower bound minimum and ending at 7333333333333333 (inclusive), does not fit within the x range, which starts from 3FFFFFFFFFFFFFFF and ends at 7FFFFFFFFFFFFFFF (inclusive). } /// /// /// /// The version of the PartitionKeyDefinition (V1 or V2) used for the validation. [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithParentIsMinInclusiveFalse() + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenYComparedToXWithXIsMinInclusiveFalse() { await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true), - childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)); + xFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", false, true), + yFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", true, false)); } /// /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenChildComparedToParentWithChildIsMinInclusiveFalse() + public async Task GivenFeedRangeThrowsArgumentOutOfRangeExceptionWhenYComparedToXWithYIsMinInclusiveFalse() { await this.FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - parentFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false), - childFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)); + xFeedRange: new Documents.Routing.Range("", "3FFFFFFFFFFFFFFF", true, false), + yFeedRange: new Documents.Routing.Range("", "FFFFFFFFFFFFFFFF", false, true)); } private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveFalse( - Documents.Routing.Range parentFeedRange, - Documents.Routing.Range childFeedRange) + Documents.Routing.Range xFeedRange, + Documents.Routing.Range yFeedRange) { try { @@ -906,8 +906,8 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF ArgumentOutOfRangeException exception = await Assert.ThrowsExceptionAsync( async () => await containerContext.Container .IsFeedRangePartOfAsync( - new FeedRangeEpk(parentFeedRange), - new FeedRangeEpk(childFeedRange), + new FeedRangeEpk(xFeedRange), + new FeedRangeEpk(yFeedRange), cancellationToken: CancellationToken.None)); if (exception == null) @@ -947,51 +947,51 @@ private async Task FeedRangeThrowsArgumentOutOfRangeExceptionWhenIsMinInclusiveF /// and max value - /// And the parent feed range is inclusive of its min value - /// And the parent feed range is inclusive of its max value - /// When a child feed range with min value and max value is compared - /// And the child feed range is inclusive of its min value - /// And the child feed range is inclusive of its max value - /// Then the result should be indicating if the child is a subset of the parent + /// Scenario Outline: Verify if the y feed range is a subset of the x feed range + /// Given a x feed range with min value and max value + /// And the x feed range is inclusive of its min value + /// And the x feed range is inclusive of its max value + /// When a y feed range with min value and max value is compared + /// And the y feed range is inclusive of its min value + /// And the y feed range is inclusive of its max value + /// Then the result should be indicating if the y is a subset of the x /// ]]> /// - /// Indicates whether the parent range's minimum value is inclusive. - /// Indicates whether the parent range's maximum value is inclusive. - /// The minimum value of the parent range. - /// The maximum value of the parent range. - /// Indicates whether the child range's minimum value is inclusive. - /// Indicates whether the child range's maximum value is inclusive. - /// The minimum value of the child range. - /// The maximum value of the child range. - /// A boolean indicating whether the child feed range is expected to be a subset of the parent feed range. True if the child is a subset, false otherwise. + /// Indicates whether the x range's minimum value is inclusive. + /// Indicates whether the x range's maximum value is inclusive. + /// The minimum value of the x range. + /// The maximum value of the x range. + /// Indicates whether the y range's minimum value is inclusive. + /// Indicates whether the y range's maximum value is inclusive. + /// The minimum value of the y range. + /// The maximum value of the y range. + /// A boolean indicating whether the y feed range is expected to be a subset of the x feed range. True if the y is a subset, false otherwise. [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both parent and child ranges (A to Z) are fully inclusive and equal, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both parent and child ranges (A to A) are fully inclusive and equal, and min and max range is the same, child is a subset")] - [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both parent and child ranges are fully inclusive but min and max ranges are not the same (A to A, B to B), child is not a subset")] - [DataRow(true, true, "B", "B", true, true, "A", "A", false, DisplayName = "(true, true) Given parent range (B to B) is fully inclusive and child range (A to A) is fully inclusive, child is not a subset")] - [DataRow(true, false, "A", "Z", true, true, "A", "Y", true, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Y) is fully inclusive, child is a subset")] - [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Y) has an exclusive max but child range (A to Z) exceeds the parent’s max with an inclusive bound, child is not a subset")] - [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given parent range (A to Z) has an exclusive max and child range (A to Z) is fully inclusive, child is not a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "(false, false) Given parent range (A to Y) is inclusive at min and exclusive at max, and child range (A to Y) is inclusive at min and exclusive at max, child is a subset")] - [DataRow(true, false, "A", "W", true, false, "A", "Y", false, DisplayName = "(false, false) Given parent range (A to W) is inclusive at min and exclusive at max, and child range (A to Y) is inclusive at min and exclusive at max, child is not a subset")] - [DataRow(true, false, "A", "Y", true, false, "A", "W", true, DisplayName = "(false, false) Given parent range (A to Y) is inclusive at min and exclusive at max, and child range (A to W) is inclusive at min and exclusive at max, child is a subset")] - public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( - bool parentIsMinInclusive, - bool parentIsMaxInclusive, - string parentMinValue, - string parentMaxValue, - bool childIsMinInclusive, - bool childIsMaxInclusive, - string childMinValue, - string childMaxValue, + [DataRow(true, true, "A", "Z", true, true, "A", "Z", true, DisplayName = "(true, true) Given both x and y ranges (A to Z) are fully inclusive and equal, y is a subset")] + [DataRow(true, true, "A", "A", true, true, "A", "A", true, DisplayName = "(true, true) Given both x and y ranges (A to A) are fully inclusive and equal, and min and max range is the same, y is a subset")] + [DataRow(true, true, "A", "A", true, true, "B", "B", false, DisplayName = "(true, true) Given both x and y ranges are fully inclusive but min and max ranges are not the same (A to A, B to B), y is not a subset")] + [DataRow(true, true, "B", "B", true, true, "A", "A", false, DisplayName = "(true, true) Given x range (B to B) is fully inclusive and y range (A to A) is fully inclusive, y is not a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Y", true, DisplayName = "(false, true) Given x range (A to Z) has an exclusive max and y range (A to Y) is fully inclusive, y is a subset")] + [DataRow(true, false, "A", "Y", true, true, "A", "Z", false, DisplayName = "(false, true) Given x range (A to Y) has an exclusive max but y range (A to Z) exceeds the x’s max with an inclusive bound, y is not a subset")] + [DataRow(true, false, "A", "Z", true, true, "A", "Z", false, DisplayName = "(false, true) Given x range (A to Z) has an exclusive max and y range (A to Z) is fully inclusive, y is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "Y", true, DisplayName = "(false, false) Given x range (A to Y) is inclusive at min and exclusive at max, and y range (A to Y) is inclusive at min and exclusive at max, y is a subset")] + [DataRow(true, false, "A", "W", true, false, "A", "Y", false, DisplayName = "(false, false) Given x range (A to W) is inclusive at min and exclusive at max, and y range (A to Y) is inclusive at min and exclusive at max, y is not a subset")] + [DataRow(true, false, "A", "Y", true, false, "A", "W", true, DisplayName = "(false, false) Given x range (A to Y) is inclusive at min and exclusive at max, and y range (A to W) is inclusive at min and exclusive at max, y is a subset")] + public void GivenXRangeWhenYRangeComparedThenValidateIfSubset( + bool xIsMinInclusive, + bool xIsMaxInclusive, + string xMinValue, + string xMaxValue, + bool yIsMinInclusive, + bool yIsMaxInclusive, + string yMinValue, + string yMaxValue, bool expectedIsSubset) { bool actualIsSubset = ContainerCore.IsSubset( - new Documents.Routing.Range(isMinInclusive: parentIsMinInclusive, isMaxInclusive: parentIsMaxInclusive, min: parentMinValue, max: parentMaxValue), - new Documents.Routing.Range(isMinInclusive: childIsMinInclusive, isMaxInclusive: childIsMaxInclusive, min: childMinValue, max: childMaxValue)); + new Documents.Routing.Range(isMinInclusive: xIsMinInclusive, isMaxInclusive: xIsMaxInclusive, min: xMinValue, max: xMaxValue), + new Documents.Routing.Range(isMinInclusive: yIsMinInclusive, isMaxInclusive: yIsMaxInclusive, min: yMinValue, max: yMaxValue)); Assert.AreEqual( expected: expectedIsSubset, @@ -1002,38 +1002,38 @@ public void GivenParentRangeWhenChildRangeComparedThenValidateIfSubset( /// /// - /// Indicates whether the parent range's minimum value is inclusive. - /// Indicates whether the parent range's maximum value is inclusive. - /// The minimum value of the parent range. - /// The maximum value of the parent range. - /// Indicates whether the child range's minimum value is inclusive. - /// Indicates whether the child range's maximum value is inclusive. - /// The minimum value of the child range. - /// The maximum value of the child range. + /// Indicates whether the x range's minimum value is inclusive. + /// Indicates whether the x range's maximum value is inclusive. + /// The minimum value of the x range. + /// The maximum value of the x range. + /// Indicates whether the y range's minimum value is inclusive. + /// Indicates whether the y range's maximum value is inclusive. + /// The minimum value of the y range. + /// The maximum value of the y range. [TestMethod] [Owner("philipthomas-MSFT")] - [DataRow(true, true, "A", "Y", true, false, "A", "W", DisplayName = "(true, false) Given parent range (A to Y) is inclusive at min and max, and child range (A to W) is inclusive at min and exclusive at max, expects NotSupportedException")] - [DataRow(true, true, "A", "Z", true, false, "A", "X", DisplayName = "(true, false) Given parent range (A to Z) is inclusive at min and max, and child range (A to X) is inclusive at min and exclusive at max, expects NotSupportedException")] - [DataRow(true, true, "A", "Y", true, false, "A", "Y", DisplayName = "(true, false) Given parent range (A to Y) is inclusive at min and max, and child range (A to Y) is inclusive at min and exclusive at max, expects NotSupportedException")] - public void GivenParentMaxInclusiveChildMaxExclusiveWhenCallingIsSubsetThenExpectNotSupportedExceptionIsThrown( - bool parentIsMinInclusive, - bool parentIsMaxInclusive, - string parentMinValue, - string parentMaxValue, - bool childIsMinInclusive, - bool childIsMaxInclusive, - string childMinValue, - string childMaxValue) + [DataRow(true, true, "A", "Y", true, false, "A", "W", DisplayName = "(true, false) Given x range (A to Y) is inclusive at min and max, and y range (A to W) is inclusive at min and exclusive at max, expects NotSupportedException")] + [DataRow(true, true, "A", "Z", true, false, "A", "X", DisplayName = "(true, false) Given x range (A to Z) is inclusive at min and max, and y range (A to X) is inclusive at min and exclusive at max, expects NotSupportedException")] + [DataRow(true, true, "A", "Y", true, false, "A", "Y", DisplayName = "(true, false) Given x range (A to Y) is inclusive at min and max, and y range (A to Y) is inclusive at min and exclusive at max, expects NotSupportedException")] + public void GivenXMaxInclusiveYMaxExclusiveWhenCallingIsSubsetThenExpectNotSupportedExceptionIsThrown( + bool xIsMinInclusive, + bool xIsMaxInclusive, + string xMinValue, + string xMaxValue, + bool yIsMinInclusive, + bool yIsMaxInclusive, + string yMinValue, + string yMaxValue) { NotSupportedException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( - parentRange: new Documents.Routing.Range(min: parentMinValue, max: parentMaxValue, isMinInclusive: parentIsMinInclusive, isMaxInclusive: parentIsMaxInclusive), - childRange: new Documents.Routing.Range(min: childMinValue, max: childMaxValue, isMinInclusive: childIsMinInclusive, isMaxInclusive: childIsMaxInclusive))); + new Documents.Routing.Range(min: xMinValue, max: xMaxValue, isMinInclusive: xIsMinInclusive, isMaxInclusive: xIsMaxInclusive), + new Documents.Routing.Range(min: yMinValue, max: yMaxValue, isMinInclusive: yIsMinInclusive, isMaxInclusive: yIsMaxInclusive))); Assert.IsNotNull(exception); } @@ -1042,20 +1042,20 @@ public void GivenParentMaxInclusiveChildMaxExclusiveWhenCallingIsSubsetThenExpec /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public void GivenNullParentFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() + public void GivenNullXFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() { ArgumentNullException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( - parentRange: null, - childRange: new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true))); + null, + new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true))); Assert.IsNotNull(exception); } @@ -1064,20 +1064,20 @@ public void GivenNullParentFeedRangeWhenCallingIsSubsetThenArgumentNullException /// /// [TestMethod] [Owner("philipthomas-MSFT")] - public void GivenNullChildFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() + public void GivenNullYFeedRangeWhenCallingIsSubsetThenArgumentNullExceptionIsThrown() { ArgumentNullException exception = Assert.ThrowsException(() => ContainerCore.IsSubset( - parentRange: new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true), - childRange: null)); + new Documents.Routing.Range(min: "A", max: "Z", isMinInclusive: true, isMaxInclusive: true), + null)); Assert.IsNotNull(exception); } From 0c4df8e09701c563fac14159f2097dd59970f7f3 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 3 Oct 2024 09:45:13 -0400 Subject: [PATCH 143/145] removed extra params --- Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 434a49ebbe..4ddea2f450 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1808,9 +1808,6 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// /// /// True if the y feed range is a part of the x feed range; otherwise, false. - /// The broader feed range representing the larger, encompassing logical partition. - /// The smaller, more granular feed range that needs to be checked for containment within the broader feed range. - /// An optional cancellation token to cancel the operation before completion. /// Returns a boolean indicating whether the y feed range is fully contained within the x feed range. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange x, From 8441b34cea66e758580590aabc6a389b96157691 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 3 Oct 2024 09:48:52 -0400 Subject: [PATCH 144/145] extraa return removed --- Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 4ddea2f450..6b3f78f91a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1806,8 +1806,7 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllV /// cancellationToken); /// ]]> /// - /// - /// True if the y feed range is a part of the x feed range; otherwise, false. + /// /// Returns a boolean indicating whether the y feed range is fully contained within the x feed range. public virtual Task IsFeedRangePartOfAsync( Cosmos.FeedRange x, From 855810bbe2b42d373fd8b2d51209a23c3cb74a6e Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 3 Oct 2024 13:36:06 -0400 Subject: [PATCH 145/145] done. --- .../Resource/Container/ContainerCore.Items.cs | 2526 ++++++++--------- 1 file changed, 1260 insertions(+), 1266 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 127a641862..34a4ee0b3a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -1,1261 +1,1258 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Numerics; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.ChangeFeed; - using Microsoft.Azure.Cosmos.ChangeFeed.FeedProcessing; - using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; - using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Json; - using Microsoft.Azure.Cosmos.Linq; - using Microsoft.Azure.Cosmos.Pagination; - using Microsoft.Azure.Cosmos.Query; - using Microsoft.Azure.Cosmos.Query.Core; - using Microsoft.Azure.Cosmos.Query.Core.Monads; - using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.ReadFeed; +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.ChangeFeed; + using Microsoft.Azure.Cosmos.ChangeFeed.FeedProcessing; + using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; + using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Linq; + using Microsoft.Azure.Cosmos.Pagination; + using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Query.Core.Monads; + using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; - using Microsoft.Azure.Cosmos.Routing; - using Microsoft.Azure.Cosmos.Serializer; - using Microsoft.Azure.Cosmos.Tracing; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Routing; - - /// - /// Used to perform operations on items. There are two different types of operations. - /// 1. The object operations where it serializes and deserializes the item on request/response - /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream - /// - internal abstract partial class ContainerCore : ContainerInternal - { - /// - /// Cache the full URI segment without the last resource id. - /// This allows only a single con-cat operation instead of building the full URI string each time. - /// - private string cachedUriSegmentWithoutId { get; } - - private readonly CosmosQueryClient queryClient; - - public async Task CreateItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - streamPayload: streamPayload, - operationType: OperationType.Create, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> CreateItemAsync( - T item, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - item: item, - operationType: OperationType.Create, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task ReadItemStreamAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Read, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> ReadItemAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage response = await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Read, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task UpsertItemStreamAsync( - Stream streamPayload, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - streamPayload: streamPayload, - operationType: OperationType.Upsert, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> UpsertItemAsync( - T item, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: null, - item: item, - operationType: OperationType.Upsert, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task ReplaceItemStreamAsync( - Stream streamPayload, - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: streamPayload, - operationType: OperationType.Replace, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> ReplaceItemAsync( - T item, - string id, - ITrace trace, - PartitionKey? partitionKey = null, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - item: item, - operationType: OperationType.Replace, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public async Task DeleteItemStreamAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - return await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Delete, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public async Task> DeleteItemAsync( - string id, - PartitionKey partitionKey, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage response = await this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: null, - operationType: OperationType.Delete, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(response); - } - - public override FeedIterator GetItemQueryStreamIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - QueryDefinition queryDefinition = null; - if (queryText != null) - { - queryDefinition = new QueryDefinition(queryText); - } - - return this.GetItemQueryStreamIterator( - queryDefinition, - continuationToken, - requestOptions); - } - - public override FeedIterator GetItemQueryStreamIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - return this.GetItemQueryStreamIteratorInternal( - sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), - isContinuationExcpected: true, - continuationToken: continuationToken, - feedRange: null, - requestOptions: requestOptions); - } - - public async Task ReadManyItemsStreamAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ITrace trace, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - PartitionKeyDefinition partitionKeyDefinition; - try - { - partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - } - catch (CosmosException ex) - { - return ex.ToCosmosResponseMessage(request: null); - } - - ReadManyHelper readManyHelper = new ReadManyQueryHelper(partitionKeyDefinition, - this); - - return await readManyHelper.ExecuteReadManyRequestAsync(items, - readManyRequestOptions, - trace, - cancellationToken); - } - - public async Task> ReadManyItemsAsync( - IReadOnlyList<(string id, PartitionKey partitionKey)> items, - ITrace trace, - ReadManyRequestOptions readManyRequestOptions = null, - CancellationToken cancellationToken = default) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), - this); - - return await readManyHelper.ExecuteReadManyRequestAsync(items, - readManyRequestOptions, - trace, - cancellationToken); - } - - public override FeedIterator GetItemQueryIterator( - string queryText = null, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - QueryDefinition queryDefinition = null; - if (queryText != null) - { - queryDefinition = new QueryDefinition(queryText); - } - - return this.GetItemQueryIterator( - queryDefinition, - continuationToken, - requestOptions); - } - - public override FeedIterator GetItemQueryIterator( - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (requestOptions.IsEffectivePartitionKeyRouting) - { - requestOptions.PartitionKey = null; - } - - if (!(this.GetItemQueryStreamIterator( - queryDefinition, - continuationToken, - requestOptions) is FeedIteratorInternal feedIterator)) - { - throw new InvalidOperationException($"Expected a FeedIteratorInternal."); - } - - return new FeedIteratorCore( - feedIterator: feedIterator, - responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); - } - - public override IOrderedQueryable GetItemLinqQueryable( - bool allowSynchronousQueryExecution = false, - string continuationToken = null, - QueryRequestOptions requestOptions = null, - CosmosLinqSerializerOptions linqSerializerOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (this.ClientContext.ClientOptions != null) - { - linqSerializerOptions ??= new CosmosLinqSerializerOptions - { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default - }; - } - - CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); - - return new CosmosLinqQuery( - this, - this.ClientContext.ResponseFactory, - (CosmosQueryClientCore)this.queryClient, - continuationToken, - requestOptions, - allowSynchronousQueryExecution, - linqSerializerOptionsInternal); - } - - public override FeedIterator GetItemQueryIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - requestOptions ??= new QueryRequestOptions(); - - if (!(this.GetItemQueryStreamIterator( - feedRange, - queryDefinition, - continuationToken, - requestOptions) is FeedIteratorInternal feedIterator)) - { - throw new InvalidOperationException($"Expected a FeedIteratorInternal."); - } - - return new FeedIteratorCore( - feedIterator: feedIterator, - responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); - } - - public override FeedIterator GetItemQueryStreamIterator( - FeedRange feedRange, - QueryDefinition queryDefinition, - string continuationToken = null, - QueryRequestOptions requestOptions = null) - { - FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; - return this.GetItemQueryStreamIteratorInternal( - sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), - isContinuationExcpected: true, - continuationToken: continuationToken, - feedRange: feedRangeInternal, - requestOptions: requestOptions); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: true); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangeFeedStreamHandler onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( - string processorName, - ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate), - withManualCheckpointing: true); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, - ChangeFeedMode.LatestVersion); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( - string processorName, - ChangesEstimationHandler estimationDelegate, - TimeSpan? estimationPeriod = null) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (estimationDelegate == null) - { - throw new ArgumentNullException(nameof(estimationDelegate)); - } - - ChangeFeedEstimatorRunner changeFeedEstimatorCore = new ChangeFeedEstimatorRunner(estimationDelegate, estimationPeriod); - return new ChangeFeedProcessorBuilder( - processorName: processorName, - container: this, - changeFeedProcessor: changeFeedEstimatorCore, - applyBuilderConfiguration: changeFeedEstimatorCore.ApplyBuildConfiguration); - } - - public override ChangeFeedEstimator GetChangeFeedEstimator( - string processorName, - Container leaseContainer) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (leaseContainer == null) - { - throw new ArgumentNullException(nameof(leaseContainer)); - } - - return new ChangeFeedEstimatorCore( - processorName: processorName, - monitoredContainer: this, - leaseContainer: (ContainerInternal)leaseContainer, - documentServiceLeaseContainer: default); - } - - public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) - { - return new BatchCore(this, partitionKey); - } - - public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( - ChangeFeedCrossFeedRangeState state, - ChangeFeedMode changeFeedMode, - ChangeFeedRequestOptions changeFeedRequestOptions = default) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + using Microsoft.Azure.Cosmos.Serializer; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Documents; + + /// + /// Used to perform operations on items. There are two different types of operations. + /// 1. The object operations where it serializes and deserializes the item on request/response + /// 2. The stream response which takes a Stream containing a JSON serialized object and returns a response containing a Stream + /// + internal abstract partial class ContainerCore : ContainerInternal + { + /// + /// Cache the full URI segment without the last resource id. + /// This allows only a single con-cat operation instead of building the full URI string each time. + /// + private string cachedUriSegmentWithoutId { get; } + + private readonly CosmosQueryClient queryClient; + + public async Task CreateItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + streamPayload: streamPayload, + operationType: OperationType.Create, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> CreateItemAsync( + T item, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + item: item, + operationType: OperationType.Create, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task ReadItemStreamAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Read, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> ReadItemAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage response = await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Read, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task UpsertItemStreamAsync( + Stream streamPayload, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + streamPayload: streamPayload, + operationType: OperationType.Upsert, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> UpsertItemAsync( + T item, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: null, + item: item, + operationType: OperationType.Upsert, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task ReplaceItemStreamAsync( + Stream streamPayload, + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: streamPayload, + operationType: OperationType.Replace, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> ReplaceItemAsync( + T item, + string id, + ITrace trace, + PartitionKey? partitionKey = null, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + ResponseMessage response = await this.ExtractPartitionKeyAndProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + item: item, + operationType: OperationType.Replace, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public async Task DeleteItemStreamAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + return await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Delete, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public async Task> DeleteItemAsync( + string id, + PartitionKey partitionKey, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage response = await this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: null, + operationType: OperationType.Delete, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(response); + } + + public override FeedIterator GetItemQueryStreamIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + QueryDefinition queryDefinition = null; + if (queryText != null) + { + queryDefinition = new QueryDefinition(queryText); + } + + return this.GetItemQueryStreamIterator( + queryDefinition, + continuationToken, + requestOptions); + } + + public override FeedIterator GetItemQueryStreamIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + return this.GetItemQueryStreamIteratorInternal( + sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), + isContinuationExcpected: true, + continuationToken: continuationToken, + feedRange: null, + requestOptions: requestOptions); + } + + public async Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + PartitionKeyDefinition partitionKeyDefinition; + try + { + partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); + } + catch (CosmosException ex) + { + return ex.ToCosmosResponseMessage(request: null); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(partitionKeyDefinition, + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + + public async Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + + public override FeedIterator GetItemQueryIterator( + string queryText = null, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + QueryDefinition queryDefinition = null; + if (queryText != null) + { + queryDefinition = new QueryDefinition(queryText); + } + + return this.GetItemQueryIterator( + queryDefinition, + continuationToken, + requestOptions); + } + + public override FeedIterator GetItemQueryIterator( + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (requestOptions.IsEffectivePartitionKeyRouting) + { + requestOptions.PartitionKey = null; + } + + if (!(this.GetItemQueryStreamIterator( + queryDefinition, + continuationToken, + requestOptions) is FeedIteratorInternal feedIterator)) + { + throw new InvalidOperationException($"Expected a FeedIteratorInternal."); + } + + return new FeedIteratorCore( + feedIterator: feedIterator, + responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); + } + + public override IOrderedQueryable GetItemLinqQueryable( + bool allowSynchronousQueryExecution = false, + string continuationToken = null, + QueryRequestOptions requestOptions = null, + CosmosLinqSerializerOptions linqSerializerOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (this.ClientContext.ClientOptions != null) + { + linqSerializerOptions ??= new CosmosLinqSerializerOptions + { + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default + }; + } + + CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); + + return new CosmosLinqQuery( + this, + this.ClientContext.ResponseFactory, + (CosmosQueryClientCore)this.queryClient, + continuationToken, + requestOptions, + allowSynchronousQueryExecution, + linqSerializerOptionsInternal); + } + + public override FeedIterator GetItemQueryIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + requestOptions ??= new QueryRequestOptions(); + + if (!(this.GetItemQueryStreamIterator( + feedRange, + queryDefinition, + continuationToken, + requestOptions) is FeedIteratorInternal feedIterator)) + { + throw new InvalidOperationException($"Expected a FeedIteratorInternal."); + } + + return new FeedIteratorCore( + feedIterator: feedIterator, + responseCreator: this.ClientContext.ResponseFactory.CreateQueryFeedUserTypeResponse); + } + + public override FeedIterator GetItemQueryStreamIterator( + FeedRange feedRange, + QueryDefinition queryDefinition, + string continuationToken = null, + QueryRequestOptions requestOptions = null) + { + FeedRangeInternal feedRangeInternal = feedRange as FeedRangeInternal; + return this.GetItemQueryStreamIteratorInternal( + sqlQuerySpec: queryDefinition?.ToSqlQuerySpec(), + isContinuationExcpected: true, + continuationToken: continuationToken, + feedRange: feedRangeInternal, + requestOptions: requestOptions); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: true); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate), + withManualCheckpointing: true); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, + ChangeFeedMode.LatestVersion); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( + string processorName, + ChangesEstimationHandler estimationDelegate, + TimeSpan? estimationPeriod = null) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (estimationDelegate == null) + { + throw new ArgumentNullException(nameof(estimationDelegate)); + } + + ChangeFeedEstimatorRunner changeFeedEstimatorCore = new ChangeFeedEstimatorRunner(estimationDelegate, estimationPeriod); + return new ChangeFeedProcessorBuilder( + processorName: processorName, + container: this, + changeFeedProcessor: changeFeedEstimatorCore, + applyBuilderConfiguration: changeFeedEstimatorCore.ApplyBuildConfiguration); + } + + public override ChangeFeedEstimator GetChangeFeedEstimator( + string processorName, + Container leaseContainer) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (leaseContainer == null) + { + throw new ArgumentNullException(nameof(leaseContainer)); + } + + return new ChangeFeedEstimatorCore( + processorName: processorName, + monitoredContainer: this, + leaseContainer: (ContainerInternal)leaseContainer, + documentServiceLeaseContainer: default); + } + + public override TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey) + { + return new BatchCore(this, partitionKey); + } + + public override IAsyncEnumerable> GetChangeFeedAsyncEnumerable( + ChangeFeedCrossFeedRangeState state, + ChangeFeedMode changeFeedMode, + ChangeFeedRequestOptions changeFeedRequestOptions = default) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - changeFeedRequestOptions: changeFeedRequestOptions); - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - Dictionary additionalHeaders; - - if ((changeFeedRequestOptions?.Properties != null) && changeFeedRequestOptions.Properties.Any()) - { - Dictionary additionalNonStringHeaders = new Dictionary(); - additionalHeaders = new Dictionary(); - foreach (KeyValuePair keyValuePair in changeFeedRequestOptions.Properties) - { - if (keyValuePair.Value is string stringValue) - { - additionalHeaders[keyValuePair.Key] = stringValue; - } - else - { - additionalNonStringHeaders[keyValuePair.Key] = keyValuePair.Value; - } - } - - changeFeedRequestOptions.Properties = additionalNonStringHeaders; - } - else - { - additionalHeaders = null; - } - - ChangeFeedExecutionOptions changeFeedPaginationOptions = new ChangeFeedExecutionOptions( - changeFeedMode, - changeFeedRequestOptions?.PageSizeHint, - changeFeedRequestOptions?.JsonSerializationFormatOptions?.JsonSerializationFormat, - additionalHeaders); - - return new ChangeFeedCrossFeedRangeAsyncEnumerable( - documentContainer, - state, - changeFeedPaginationOptions, - changeFeedRequestOptions?.JsonSerializationFormatOptions); - } - - public override FeedIterator GetStandByFeedIterator( - string continuationToken = null, - int? maxItemCount = null, - StandByFeedIteratorRequestOptions requestOptions = null) - { - StandByFeedIteratorRequestOptions cosmosQueryRequestOptions = requestOptions ?? new StandByFeedIteratorRequestOptions(); - - return new StandByFeedIteratorCore( - clientContext: this.ClientContext, - continuationToken: continuationToken, - maxItemCount: maxItemCount, - container: this, - options: cosmosQueryRequestOptions); - } - - /// - /// Helper method to create a stream feed iterator. - /// It decides if it is a query or read feed and create - /// the correct instance. - /// - public override FeedIteratorInternal GetItemQueryStreamIteratorInternal( - SqlQuerySpec sqlQuerySpec, - bool isContinuationExcpected, - string continuationToken, - FeedRangeInternal feedRange, - QueryRequestOptions requestOptions) - { - requestOptions ??= new QueryRequestOptions(); - - if (requestOptions.IsEffectivePartitionKeyRouting) - { - if (feedRange != null) - { - throw new ArgumentException(nameof(feedRange), ClientResources.FeedToken_EffectivePartitionKeyRouting); - } - - requestOptions.PartitionKey = null; - } - - if (sqlQuerySpec == null) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + Guid.NewGuid(), + changeFeedRequestOptions: changeFeedRequestOptions); + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + Dictionary additionalHeaders; + + if ((changeFeedRequestOptions?.Properties != null) && changeFeedRequestOptions.Properties.Any()) + { + Dictionary additionalNonStringHeaders = new Dictionary(); + additionalHeaders = new Dictionary(); + foreach (KeyValuePair keyValuePair in changeFeedRequestOptions.Properties) + { + if (keyValuePair.Value is string stringValue) + { + additionalHeaders[keyValuePair.Key] = stringValue; + } + else + { + additionalNonStringHeaders[keyValuePair.Key] = keyValuePair.Value; + } + } + + changeFeedRequestOptions.Properties = additionalNonStringHeaders; + } + else + { + additionalHeaders = null; + } + + ChangeFeedExecutionOptions changeFeedPaginationOptions = new ChangeFeedExecutionOptions( + changeFeedMode, + changeFeedRequestOptions?.PageSizeHint, + changeFeedRequestOptions?.JsonSerializationFormatOptions?.JsonSerializationFormat, + additionalHeaders); + + return new ChangeFeedCrossFeedRangeAsyncEnumerable( + documentContainer, + state, + changeFeedPaginationOptions, + changeFeedRequestOptions?.JsonSerializationFormatOptions); + } + + public override FeedIterator GetStandByFeedIterator( + string continuationToken = null, + int? maxItemCount = null, + StandByFeedIteratorRequestOptions requestOptions = null) + { + StandByFeedIteratorRequestOptions cosmosQueryRequestOptions = requestOptions ?? new StandByFeedIteratorRequestOptions(); + + return new StandByFeedIteratorCore( + clientContext: this.ClientContext, + continuationToken: continuationToken, + maxItemCount: maxItemCount, + container: this, + options: cosmosQueryRequestOptions); + } + + /// + /// Helper method to create a stream feed iterator. + /// It decides if it is a query or read feed and create + /// the correct instance. + /// + public override FeedIteratorInternal GetItemQueryStreamIteratorInternal( + SqlQuerySpec sqlQuerySpec, + bool isContinuationExcpected, + string continuationToken, + FeedRangeInternal feedRange, + QueryRequestOptions requestOptions) + { + requestOptions ??= new QueryRequestOptions(); + + if (requestOptions.IsEffectivePartitionKeyRouting) + { + if (feedRange != null) + { + throw new ArgumentException(nameof(feedRange), ClientResources.FeedToken_EffectivePartitionKeyRouting); + } + + requestOptions.PartitionKey = null; + } + + if (sqlQuerySpec == null) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - requestOptions); - - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((requestOptions.Properties != null) && requestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: requestOptions.MaxItemCount ?? int.MaxValue); - - return new ReadFeedIteratorCore( - documentContainer, - continuationToken, - readFeedPaginationOptions, - requestOptions, - this, - cancellationToken: default); - } - - return QueryIterator.Create( - containerCore: this, - client: this.queryClient, - clientContext: this.ClientContext, - sqlQuerySpec: sqlQuerySpec, - continuationToken: continuationToken, - feedRangeInternal: feedRange, - queryRequestOptions: requestOptions, - resourceLink: this.LinkUri, - isContinuationExpected: isContinuationExcpected, - allowNonValueAggregateQuery: true, - partitionedQueryExecutionInfo: null, - resourceType: ResourceType.Document); - } - - public override FeedIteratorInternal GetReadFeedIterator( - QueryDefinition queryDefinition, - QueryRequestOptions queryRequestOptions, - string resourceLink, - ResourceType resourceType, - string continuationToken, - int pageSize) - { - queryRequestOptions ??= new QueryRequestOptions(); - - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, + Guid.NewGuid(), + requestOptions); + + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((requestOptions.Properties != null) && requestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: requestOptions.MaxItemCount ?? int.MaxValue); + + return new ReadFeedIteratorCore( + documentContainer, + continuationToken, + readFeedPaginationOptions, + requestOptions, + this, + cancellationToken: default); + } + + return QueryIterator.Create( + containerCore: this, + client: this.queryClient, + clientContext: this.ClientContext, + sqlQuerySpec: sqlQuerySpec, + continuationToken: continuationToken, + feedRangeInternal: feedRange, + queryRequestOptions: requestOptions, + resourceLink: this.LinkUri, + isContinuationExpected: isContinuationExcpected, + allowNonValueAggregateQuery: true, + partitionedQueryExecutionInfo: null, + resourceType: ResourceType.Document); + } + + public override FeedIteratorInternal GetReadFeedIterator( + QueryDefinition queryDefinition, + QueryRequestOptions queryRequestOptions, + string resourceLink, + ResourceType resourceType, + string continuationToken, + int pageSize) + { + queryRequestOptions ??= new QueryRequestOptions(); + + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, this.queryClient, - Guid.NewGuid(), - queryRequestOptions, - resourceLink: resourceLink, - resourceType: resourceType); - - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - FeedIteratorInternal feedIterator; - if (queryDefinition != null) - { - feedIterator = QueryIterator.Create( - containerCore: this, - client: this.queryClient, - clientContext: this.ClientContext, - sqlQuerySpec: queryDefinition.ToSqlQuerySpec(), - continuationToken: continuationToken, - feedRangeInternal: FeedRangeEpk.FullRange, - queryRequestOptions: queryRequestOptions, - resourceLink: resourceLink, - isContinuationExpected: false, - allowNonValueAggregateQuery: true, - partitionedQueryExecutionInfo: null, - resourceType: resourceType); - } - else - { - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((queryRequestOptions.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: queryRequestOptions.MaxItemCount ?? int.MaxValue); - - feedIterator = new ReadFeedIteratorCore( - documentContainer: documentContainer, - queryRequestOptions: queryRequestOptions, - continuationToken: continuationToken, - readFeedPaginationOptions: readFeedPaginationOptions, - container: this, - cancellationToken: default); - } - - return feedIterator; - } - - public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( - ReadFeedCrossFeedRangeState state, - QueryRequestOptions queryRequestOptions = default) - { - NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( - this, - this.queryClient, - Guid.NewGuid(), - queryRequestOptions); - DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); - - ReadFeedExecutionOptions.PaginationDirection? direction = null; - if ((queryRequestOptions?.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) - { - direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; - } - - ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( - direction, - pageSizeHint: queryRequestOptions?.MaxItemCount); - - return new ReadFeedCrossFeedRangeAsyncEnumerable( - documentContainer, - state, - readFeedPaginationOptions); - } - - // Extracted partition key might be invalid as CollectionCache might be stale. - // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy - // and partition-key is extracted again. - private async Task ExtractPartitionKeyAndProcessItemStreamAsync( - PartitionKey? partitionKey, - string itemId, - T item, - OperationType operationType, - ItemRequestOptions requestOptions, - ITrace trace, - CancellationToken cancellationToken) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - Stream itemStream; - using (trace.StartChild("ItemSerialize")) - { - itemStream = this.ClientContext.SerializerCore.ToStream(item); - } - - // User specified PK value, no need to extract it - if (partitionKey.HasValue) - { - return await this.ProcessItemStreamAsync( - partitionKey, - itemId, - itemStream, - operationType, - requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - PartitionKeyMismatchRetryPolicy requestRetryPolicy = null; - while (true) - { - partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, trace, cancellationToken); - - ResponseMessage responseMessage = await this.ProcessItemStreamAsync( - partitionKey, - itemId, - itemStream, - operationType, - requestOptions, - trace: trace, - cancellationToken: cancellationToken); - - if (responseMessage.IsSuccessStatusCode) - { - return responseMessage; - } - - if (requestRetryPolicy == null) - { - requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( - await this.ClientContext.DocumentClient.GetCollectionCacheAsync(trace), - requestRetryPolicy); - } - - ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); - if (!retryResult.ShouldRetry) - { - return responseMessage; - } - } - } - - private async Task ProcessItemStreamAsync( - PartitionKey? partitionKey, - string itemId, - Stream streamPayload, - OperationType operationType, - ItemRequestOptions requestOptions, - ITrace trace, - CancellationToken cancellationToken) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting) - { - partitionKey = null; - } - - ContainerInternal.ValidatePartitionKey(partitionKey, requestOptions); - string resourceUri = this.GetResourceUri(requestOptions, operationType, itemId); - - ResponseMessage responseMessage = await this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: resourceUri, - resourceType: ResourceType.Document, - operationType: operationType, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: partitionKey, - itemId: itemId, - streamPayload: streamPayload, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - - return responseMessage; - } - - public override async Task GetPartitionKeyValueFromStreamAsync( - Stream stream, - ITrace trace, - CancellationToken cancellation = default) - { - if (!stream.CanSeek) - { - throw new ArgumentException("Stream needs to be seekable", nameof(stream)); - } - - using (ITrace childTrace = trace.StartChild("Get PkValue From Stream", TraceComponent.Routing, Tracing.TraceLevel.Info)) - { - try - { - stream.Position = 0; - - if (!(stream is MemoryStream memoryStream)) - { - memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - } - - // TODO: Avoid copy - IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); - IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); - CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); - - IReadOnlyList> tokenslist = await this.GetPartitionKeyPathTokensAsync(childTrace, cancellation); - List cosmosElementList = new List(tokenslist.Count); - - foreach (IReadOnlyList tokenList in tokenslist) - { - if (ContainerCore.TryParseTokenListForElement(pathTraversal, tokenList, out CosmosElement element)) - { - cosmosElementList.Add(element); - } - else - { - cosmosElementList.Add(null); - } - } - - return ContainerCore.CosmosElementToPartitionKeyObject(cosmosElementList); - } - finally - { - // MemoryStream casting leverage might change position - stream.Position = 0; - } - } - } - - public Task DeleteAllItemsByPartitionKeyStreamAsync( - Cosmos.PartitionKey partitionKey, - ITrace trace, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - PartitionKey? resultingPartitionKey = requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting ? null : (PartitionKey?)partitionKey; - ContainerCore.ValidatePartitionKey(resultingPartitionKey, requestOptions); - - return this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: this.LinkUri, - resourceType: ResourceType.PartitionKey, - operationType: OperationType.Delete, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: resultingPartitionKey, - itemId: null, - streamPayload: null, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - } - - private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IReadOnlyList tokens, out CosmosElement result) - { - result = null; - for (int i = 0; i < tokens.Count - 1; i++) - { - if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) - { - return false; - } - } - - if (!pathTraversal.TryGetValue(tokens[tokens.Count - 1], out result)) - { - return false; - } - - return true; - } - - private static PartitionKey CosmosElementToPartitionKeyObject(IReadOnlyList cosmosElementList) - { - PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); - - foreach (CosmosElement cosmosElement in cosmosElementList) - { - if (cosmosElement == null) - { - partitionKeyBuilder.AddNoneType(); - } - else - { - _ = cosmosElement switch - { - CosmosString cosmosString => partitionKeyBuilder.Add(cosmosString.Value), - CosmosNumber cosmosNumber => partitionKeyBuilder.Add(Number64.ToDouble(cosmosNumber.Value)), - CosmosBoolean cosmosBoolean => partitionKeyBuilder.Add(cosmosBoolean.Value), - CosmosNull _ => partitionKeyBuilder.AddNullValue(), - _ => throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - RMResources.UnsupportedPartitionKeyComponentValue, - cosmosElement)), - }; - } - } - - return partitionKeyBuilder.Build(); - } - - private string GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) - { - if (requestOptions != null && requestOptions.TryGetResourceUri(out Uri resourceUri)) - { - return resourceUri.OriginalString; - } - - switch (operationType) - { - case OperationType.Create: - case OperationType.Upsert: - return this.LinkUri; - - default: - return this.ContcatCachedUriWithId(itemId); - } - } - - /// - /// Gets the full resource segment URI without the last id. - /// - /// Example: /dbs/*/colls/*/{this.pathSegment}/ - private string GetResourceSegmentUriWithoutId() - { - // StringBuilder is roughly 2x faster than string.Format - StringBuilder stringBuilder = new StringBuilder(this.LinkUri.Length + - Paths.DocumentsPathSegment.Length + 2); - stringBuilder.Append(this.LinkUri); - stringBuilder.Append("/"); - stringBuilder.Append(Paths.DocumentsPathSegment); - stringBuilder.Append("/"); - return stringBuilder.ToString(); - } - - /// - /// Gets the full resource URI using the cached resource URI segment - /// - /// The resource id - /// - /// A document link in the format of {CachedUriSegmentWithoutId}/{0}/ with {0} being a Uri escaped version of the - /// - /// Would be used when creating an , or when replacing or deleting a item in Azure Cosmos DB. - /// - private string ContcatCachedUriWithId(string resourceId) - { - Debug.Assert(this.cachedUriSegmentWithoutId.EndsWith("/")); - return this.cachedUriSegmentWithoutId + Uri.EscapeUriString(resourceId); - } - - public async Task> PatchItemAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - ITrace trace, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - ResponseMessage responseMessage = await this.PatchItemStreamAsync( - id, - partitionKey, - patchOperations, - trace, - requestOptions, - cancellationToken); - - return this.ClientContext.ResponseFactory.CreateItemResponse(responseMessage); - } - - public Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - IReadOnlyList patchOperations, - ITrace trace, - PatchItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException(nameof(id)); - } - - if (partitionKey == null) - { - throw new ArgumentNullException(nameof(partitionKey)); - } - - if (patchOperations == null || - !patchOperations.Any()) - { - throw new ArgumentNullException(nameof(patchOperations)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - Stream patchOperationsStream; - using (ITrace serializeTrace = trace.StartChild("Patch Operations Serialize")) - { - patchOperationsStream = this.ClientContext.SerializerCore.ToStream(new PatchSpec(patchOperations, requestOptions)); - } - - return this.ClientContext.ProcessResourceOperationStreamAsync( - resourceUri: this.GetResourceUri( - requestOptions, - OperationType.Patch, - id), - resourceType: ResourceType.Document, - operationType: OperationType.Patch, - requestOptions: requestOptions, - cosmosContainerCore: this, - partitionKey: partitionKey, - itemId: id, - streamPayload: patchOperationsStream, - requestEnricher: null, - trace: trace, - cancellationToken: cancellationToken); - } - - public Task PatchItemStreamAsync( - string id, - PartitionKey partitionKey, - Stream streamPayload, - ITrace trace, - ItemRequestOptions requestOptions = null, - CancellationToken cancellationToken = default) - { - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - if (partitionKey == null) - { - throw new ArgumentNullException(nameof(partitionKey)); - } - - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - - if (streamPayload == null) - { - throw new ArgumentNullException(nameof(streamPayload)); - } - - if (trace == null) - { - throw new ArgumentNullException(nameof(trace)); - } - - return this.ProcessItemStreamAsync( - partitionKey: partitionKey, - itemId: id, - streamPayload: streamPayload, - operationType: OperationType.Patch, - requestOptions: requestOptions, - trace: trace, - cancellationToken: cancellationToken); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( - string processorName, - ChangeFeedHandler> onChangesDelegate) - { - if (processorName == null) - { - throw new ArgumentNullException(nameof(processorName)); - } - - if (onChangesDelegate == null) - { - throw new ArgumentNullException(nameof(onChangesDelegate)); - } - - ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( - new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), - withManualCheckpointing: false); - return this.GetChangeFeedProcessorBuilderPrivate(processorName, - observerFactory, ChangeFeedMode.AllVersionsAndDeletes); - } - - private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( - string processorName, - ChangeFeedObserverFactory observerFactory, - ChangeFeedMode mode) - { - ChangeFeedProcessorCore changeFeedProcessor = new ChangeFeedProcessorCore(observerFactory); - return new ChangeFeedProcessorBuilder( - processorName: processorName, - container: this, - changeFeedProcessor: changeFeedProcessor, - applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); + Guid.NewGuid(), + queryRequestOptions, + resourceLink: resourceLink, + resourceType: resourceType); + + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + FeedIteratorInternal feedIterator; + if (queryDefinition != null) + { + feedIterator = QueryIterator.Create( + containerCore: this, + client: this.queryClient, + clientContext: this.ClientContext, + sqlQuerySpec: queryDefinition.ToSqlQuerySpec(), + continuationToken: continuationToken, + feedRangeInternal: FeedRangeEpk.FullRange, + queryRequestOptions: queryRequestOptions, + resourceLink: resourceLink, + isContinuationExpected: false, + allowNonValueAggregateQuery: true, + partitionedQueryExecutionInfo: null, + resourceType: resourceType); + } + else + { + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((queryRequestOptions.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: queryRequestOptions.MaxItemCount ?? int.MaxValue); + + feedIterator = new ReadFeedIteratorCore( + documentContainer: documentContainer, + queryRequestOptions: queryRequestOptions, + continuationToken: continuationToken, + readFeedPaginationOptions: readFeedPaginationOptions, + container: this, + cancellationToken: default); + } + + return feedIterator; + } + + public override IAsyncEnumerable> GetReadFeedAsyncEnumerable( + ReadFeedCrossFeedRangeState state, + QueryRequestOptions queryRequestOptions = default) + { + NetworkAttachedDocumentContainer networkAttachedDocumentContainer = new NetworkAttachedDocumentContainer( + this, + this.queryClient, + Guid.NewGuid(), + queryRequestOptions); + DocumentContainer documentContainer = new DocumentContainer(networkAttachedDocumentContainer); + + ReadFeedExecutionOptions.PaginationDirection? direction = null; + if ((queryRequestOptions?.Properties != null) && queryRequestOptions.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object enumerationDirection)) + { + direction = (byte)enumerationDirection == (byte)RntbdConstants.RntdbEnumerationDirection.Reverse ? ReadFeedExecutionOptions.PaginationDirection.Reverse : ReadFeedExecutionOptions.PaginationDirection.Forward; + } + + ReadFeedExecutionOptions readFeedPaginationOptions = new ReadFeedExecutionOptions( + direction, + pageSizeHint: queryRequestOptions?.MaxItemCount); + + return new ReadFeedCrossFeedRangeAsyncEnumerable( + documentContainer, + state, + readFeedPaginationOptions); + } + + // Extracted partition key might be invalid as CollectionCache might be stale. + // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy + // and partition-key is extracted again. + private async Task ExtractPartitionKeyAndProcessItemStreamAsync( + PartitionKey? partitionKey, + string itemId, + T item, + OperationType operationType, + ItemRequestOptions requestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + Stream itemStream; + using (trace.StartChild("ItemSerialize")) + { + itemStream = this.ClientContext.SerializerCore.ToStream(item); + } + + // User specified PK value, no need to extract it + if (partitionKey.HasValue) + { + return await this.ProcessItemStreamAsync( + partitionKey, + itemId, + itemStream, + operationType, + requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + PartitionKeyMismatchRetryPolicy requestRetryPolicy = null; + while (true) + { + partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, trace, cancellationToken); + + ResponseMessage responseMessage = await this.ProcessItemStreamAsync( + partitionKey, + itemId, + itemStream, + operationType, + requestOptions, + trace: trace, + cancellationToken: cancellationToken); + + if (responseMessage.IsSuccessStatusCode) + { + return responseMessage; + } + + if (requestRetryPolicy == null) + { + requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( + await this.ClientContext.DocumentClient.GetCollectionCacheAsync(trace), + requestRetryPolicy); + } + + ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); + if (!retryResult.ShouldRetry) + { + return responseMessage; + } + } + } + + private async Task ProcessItemStreamAsync( + PartitionKey? partitionKey, + string itemId, + Stream streamPayload, + OperationType operationType, + ItemRequestOptions requestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting) + { + partitionKey = null; + } + + ContainerInternal.ValidatePartitionKey(partitionKey, requestOptions); + string resourceUri = this.GetResourceUri(requestOptions, operationType, itemId); + + ResponseMessage responseMessage = await this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: resourceUri, + resourceType: ResourceType.Document, + operationType: operationType, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: partitionKey, + itemId: itemId, + streamPayload: streamPayload, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + + return responseMessage; + } + + public override async Task GetPartitionKeyValueFromStreamAsync( + Stream stream, + ITrace trace, + CancellationToken cancellation = default) + { + if (!stream.CanSeek) + { + throw new ArgumentException("Stream needs to be seekable", nameof(stream)); + } + + using (ITrace childTrace = trace.StartChild("Get PkValue From Stream", TraceComponent.Routing, Tracing.TraceLevel.Info)) + { + try + { + stream.Position = 0; + + if (!(stream is MemoryStream memoryStream)) + { + memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + } + + // TODO: Avoid copy + IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); + IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); + CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); + + IReadOnlyList> tokenslist = await this.GetPartitionKeyPathTokensAsync(childTrace, cancellation); + List cosmosElementList = new List(tokenslist.Count); + + foreach (IReadOnlyList tokenList in tokenslist) + { + if (ContainerCore.TryParseTokenListForElement(pathTraversal, tokenList, out CosmosElement element)) + { + cosmosElementList.Add(element); + } + else + { + cosmosElementList.Add(null); + } + } + + return ContainerCore.CosmosElementToPartitionKeyObject(cosmosElementList); + } + finally + { + // MemoryStream casting leverage might change position + stream.Position = 0; + } + } + } + + public Task DeleteAllItemsByPartitionKeyStreamAsync( + Cosmos.PartitionKey partitionKey, + ITrace trace, + RequestOptions requestOptions = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + PartitionKey? resultingPartitionKey = requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting ? null : (PartitionKey?)partitionKey; + ContainerCore.ValidatePartitionKey(resultingPartitionKey, requestOptions); + + return this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: this.LinkUri, + resourceType: ResourceType.PartitionKey, + operationType: OperationType.Delete, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: resultingPartitionKey, + itemId: null, + streamPayload: null, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + } + + private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IReadOnlyList tokens, out CosmosElement result) + { + result = null; + for (int i = 0; i < tokens.Count - 1; i++) + { + if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) + { + return false; + } + } + + if (!pathTraversal.TryGetValue(tokens[tokens.Count - 1], out result)) + { + return false; + } + + return true; + } + + private static PartitionKey CosmosElementToPartitionKeyObject(IReadOnlyList cosmosElementList) + { + PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); + + foreach (CosmosElement cosmosElement in cosmosElementList) + { + if (cosmosElement == null) + { + partitionKeyBuilder.AddNoneType(); + } + else + { + _ = cosmosElement switch + { + CosmosString cosmosString => partitionKeyBuilder.Add(cosmosString.Value), + CosmosNumber cosmosNumber => partitionKeyBuilder.Add(Number64.ToDouble(cosmosNumber.Value)), + CosmosBoolean cosmosBoolean => partitionKeyBuilder.Add(cosmosBoolean.Value), + CosmosNull _ => partitionKeyBuilder.AddNullValue(), + _ => throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + RMResources.UnsupportedPartitionKeyComponentValue, + cosmosElement)), + }; + } + } + + return partitionKeyBuilder.Build(); + } + + private string GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) + { + if (requestOptions != null && requestOptions.TryGetResourceUri(out Uri resourceUri)) + { + return resourceUri.OriginalString; + } + + switch (operationType) + { + case OperationType.Create: + case OperationType.Upsert: + return this.LinkUri; + + default: + return this.ContcatCachedUriWithId(itemId); + } + } + + /// + /// Gets the full resource segment URI without the last id. + /// + /// Example: /dbs/*/colls/*/{this.pathSegment}/ + private string GetResourceSegmentUriWithoutId() + { + // StringBuilder is roughly 2x faster than string.Format + StringBuilder stringBuilder = new StringBuilder(this.LinkUri.Length + + Paths.DocumentsPathSegment.Length + 2); + stringBuilder.Append(this.LinkUri); + stringBuilder.Append("/"); + stringBuilder.Append(Paths.DocumentsPathSegment); + stringBuilder.Append("/"); + return stringBuilder.ToString(); + } + + /// + /// Gets the full resource URI using the cached resource URI segment + /// + /// The resource id + /// + /// A document link in the format of {CachedUriSegmentWithoutId}/{0}/ with {0} being a Uri escaped version of the + /// + /// Would be used when creating an , or when replacing or deleting a item in Azure Cosmos DB. + /// + private string ContcatCachedUriWithId(string resourceId) + { + Debug.Assert(this.cachedUriSegmentWithoutId.EndsWith("/")); + return this.cachedUriSegmentWithoutId + Uri.EscapeUriString(resourceId); + } + + public async Task> PatchItemAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + ITrace trace, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + ResponseMessage responseMessage = await this.PatchItemStreamAsync( + id, + partitionKey, + patchOperations, + trace, + requestOptions, + cancellationToken); + + return this.ClientContext.ResponseFactory.CreateItemResponse(responseMessage); + } + + public Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + ITrace trace, + PatchItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (patchOperations == null || + !patchOperations.Any()) + { + throw new ArgumentNullException(nameof(patchOperations)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + Stream patchOperationsStream; + using (ITrace serializeTrace = trace.StartChild("Patch Operations Serialize")) + { + patchOperationsStream = this.ClientContext.SerializerCore.ToStream(new PatchSpec(patchOperations, requestOptions)); + } + + return this.ClientContext.ProcessResourceOperationStreamAsync( + resourceUri: this.GetResourceUri( + requestOptions, + OperationType.Patch, + id), + resourceType: ResourceType.Document, + operationType: OperationType.Patch, + requestOptions: requestOptions, + cosmosContainerCore: this, + partitionKey: partitionKey, + itemId: id, + streamPayload: patchOperationsStream, + requestEnricher: null, + trace: trace, + cancellationToken: cancellationToken); + } + + public Task PatchItemStreamAsync( + string id, + PartitionKey partitionKey, + Stream streamPayload, + ITrace trace, + ItemRequestOptions requestOptions = null, + CancellationToken cancellationToken = default) + { + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (streamPayload == null) + { + throw new ArgumentNullException(nameof(streamPayload)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + return this.ProcessItemStreamAsync( + partitionKey: partitionKey, + itemId: id, + streamPayload: streamPayload, + operationType: OperationType.Patch, + requestOptions: requestOptions, + trace: trace, + cancellationToken: cancellationToken); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes( + string processorName, + ChangeFeedHandler> onChangesDelegate) + { + if (processorName == null) + { + throw new ArgumentNullException(nameof(processorName)); + } + + if (onChangesDelegate == null) + { + throw new ArgumentNullException(nameof(onChangesDelegate)); + } + + ChangeFeedObserverFactory observerFactory = new CheckpointerObserverFactory( + new ChangeFeedObserverFactoryCore(onChangesDelegate, this.ClientContext.SerializerCore), + withManualCheckpointing: false); + return this.GetChangeFeedProcessorBuilderPrivate(processorName, + observerFactory, ChangeFeedMode.AllVersionsAndDeletes); + } + + private ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderPrivate( + string processorName, + ChangeFeedObserverFactory observerFactory, + ChangeFeedMode mode) + { + ChangeFeedProcessorCore changeFeedProcessor = new ChangeFeedProcessorCore(observerFactory); + return new ChangeFeedProcessorBuilder( + processorName: processorName, + container: this, + changeFeedProcessor: changeFeedProcessor, + applyBuilderConfiguration: changeFeedProcessor.ApplyBuildConfiguration).WithChangeFeedMode(mode); } /// @@ -1318,15 +1315,13 @@ public override async Task IsFeedRangePartOfAsync( trace: trace, cancellationToken: cancellationToken); - IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); - - List> xEffectiveRanges = await xFeedRangeInternal.GetEffectiveRangesAsync( + Routing.IRoutingMapProvider routingMapProvider = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace); + List> xEffectiveRanges = await xFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, trace: trace); - - List> yEffectiveRanges = await yFeedRangeInternal.GetEffectiveRangesAsync( + List> yEffectiveRanges = await yFeedRangeInternal.GetEffectiveRangesAsync( routingMapProvider: routingMapProvider, containerRid: containerRId, partitionKeyDefinition: partitionKeyDefinition, @@ -1452,10 +1447,9 @@ private static Documents.Routing.Range MergeRanges( /// ]]> /// internal static void EnsureConsistentInclusivity(List> ranges) -{ + { bool areAnyDifferent = false; - - Range firstRange = ranges[0]; + Documents.Routing.Range firstRange = ranges[0]; foreach (Documents.Routing.Range range in ranges.Skip(1)) { @@ -1555,8 +1549,8 @@ internal static bool IsSubset( (true, false) => throw new NotSupportedException("The combination where the x range's maximum is inclusive and the y range's maximum is exclusive is not supported in the current implementation."), _ => ContainerCore.IsYMaxWithinX(x, y) // Default for the following combinations: - // (true, true): Both max values are inclusive - // (false, false): Both max values are exclusive + // (true, true): Both max values are inclusive + // (false, false): Both max values are exclusive }; bool isMinWithinX = x.Contains(y.Min); @@ -1575,6 +1569,6 @@ private static bool IsYMaxWithinX( Documents.Routing.Range y) { return x.Max == y.Max || x.Contains(y.Max); - } - } -} + } + } +}