diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs index 96b889832e..bb322f19b1 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs @@ -161,6 +161,14 @@ public virtual async Task SendAsync( if (feedRange != null) { + if (!request.OperationType.IsPointOperation()) + { + feedRange = await RequestInvokerHandler.ResolveFeedRangeBasedOnPrefixContainerAsync( + feedRange: feedRange, + cosmosContainerCore: cosmosContainerCore, + cancellationToken: cancellationToken); + } + if (feedRange is FeedRangePartitionKey feedRangePartitionKey) { if (cosmosContainerCore == null && object.ReferenceEquals(feedRangePartitionKey.PartitionKey, Cosmos.PartitionKey.None)) @@ -480,5 +488,26 @@ private static bool IsClientNoResponseSet(CosmosClientOptions clientOptions, Ope && clientOptions.EnableContentResponseOnWrite.HasValue && RequestInvokerHandler.IsItemNoRepsonseSet(clientOptions.EnableContentResponseOnWrite.Value, operationType); } + + internal static async Task ResolveFeedRangeBasedOnPrefixContainerAsync( + FeedRange feedRange, + ContainerInternal cosmosContainerCore, + CancellationToken cancellationToken) + { + if (feedRange is FeedRangePartitionKey feedRangePartitionKey) + { + PartitionKeyDefinition partitionKeyDefinition = await cosmosContainerCore + .GetPartitionKeyDefinitionAsync(cancellationToken) + .ConfigureAwait(false); + + if (partitionKeyDefinition != null && partitionKeyDefinition.Kind == PartitionKind.MultiHash + && feedRangePartitionKey.PartitionKey.InternalKey?.Components?.Count < partitionKeyDefinition.Paths?.Count) + { + feedRange = new FeedRangeEpk(feedRangePartitionKey.PartitionKey.InternalKey.GetEPKRangeForPrefixPartitionKey(partitionKeyDefinition)); + } + } + + return feedRange; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 0018eca9b7..31a3ccc5ca 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -959,12 +959,6 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync // User specified PK value, no need to extract it if (partitionKey.HasValue) { - PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - if (partitionKey.HasValue && partitionKey.Value != PartitionKey.None && partitionKey.Value.InternalKey.Components.Count != pKeyDefinition.Paths.Count) - { - throw new ArgumentException(RMResources.MissingPartitionKeyValue); - } - return await this.ProcessItemStreamAsync( partitionKey, itemId, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml index ed39221a61..041523ff8a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml @@ -38,6 +38,12 @@ │ ) ├── ItemSerialize(00000000-0000-0000-0000-000000000000) Transport-Component 00:00:00:000 0.00 milliseconds ├── Get Collection Cache(00000000-0000-0000-0000-000000000000) Routing-Component 00:00:00:000 0.00 milliseconds + │ └── Waiting for Initialization of client to complete(00000000-0000-0000-0000-000000000000) Unknown-Component 00:00:00:000 0.00 milliseconds + ├── Read Collection(00000000-0000-0000-0000-000000000000) Transport-Component 00:00:00:000 0.00 milliseconds + │ ( + │ [Client Side Request Stats] + │ Redacted To Not Change The Baselines From Run To Run + │ ) ├── Batch Dispatch Async(00000000-0000-0000-0000-000000000000) Batch-Component 00:00:00:000 0.00 milliseconds │ ├── Using Wait(00000000-0000-0000-0000-000000000000) Batch-Component 00:00:00:000 0.00 milliseconds │ ├── Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler(00000000-0000-0000-0000-000000000000) RequestHandler-Component 00:00:00:000 0.00 milliseconds @@ -72,7 +78,20 @@ }, { "name": "Get Collection Cache", - "duration in milliseconds": 0 + "duration in milliseconds": 0, + "children": [ + { + "name": "Waiting for Initialization of client to complete", + "duration in milliseconds": 0 + } + ] + }, + { + "name": "Read Collection", + "duration in milliseconds": 0, + "data": { + "Client Side Request Stats": "Redacted To Not Change The Baselines From Run To Run" + } }, { "name": "Batch Dispatch Async", @@ -1634,6 +1653,12 @@ │ ) ├── ItemSerialize(00000000-0000-0000-0000-000000000000) Transport-Component 00:00:00:000 0.00 milliseconds ├── Get Collection Cache(00000000-0000-0000-0000-000000000000) Routing-Component 00:00:00:000 0.00 milliseconds + │ └── Waiting for Initialization of client to complete(00000000-0000-0000-0000-000000000000) Unknown-Component 00:00:00:000 0.00 milliseconds + ├── Read Collection(00000000-0000-0000-0000-000000000000) Transport-Component 00:00:00:000 0.00 milliseconds + │ ( + │ [Client Side Request Stats] + │ Redacted To Not Change The Baselines From Run To Run + │ ) ├── Batch Dispatch Async(00000000-0000-0000-0000-000000000000) Batch-Component 00:00:00:000 0.00 milliseconds │ ├── Using Wait(00000000-0000-0000-0000-000000000000) Batch-Component 00:00:00:000 0.00 milliseconds │ ├── Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler(00000000-0000-0000-0000-000000000000) RequestHandler-Component 00:00:00:000 0.00 milliseconds @@ -1808,7 +1833,20 @@ }, { "name": "Get Collection Cache", - "duration in milliseconds": 0 + "duration in milliseconds": 0, + "children": [ + { + "name": "Waiting for Initialization of client to complete", + "duration in milliseconds": 0 + } + ] + }, + { + "name": "Read Collection", + "duration in milliseconds": 0, + "data": { + "Client Side Request Stats": "Redacted To Not Change The Baselines From Run To Run" + } }, { "name": "Batch Dispatch Async", diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosMultiHashTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosMultiHashTest.cs index c77352d07f..3c590092db 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosMultiHashTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosMultiHashTest.cs @@ -1,5 +1,4 @@ -#if PREVIEW -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; using System.Collections.Generic; @@ -13,9 +12,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests [TestClass] public class CosmosMultiHashTest { + private CosmosClient client = null; private Cosmos.Database database = null; - private CosmosClient client = null; private Container container = null; private ContainerProperties containerProperties = null; @@ -29,7 +28,7 @@ public async Task TestInitialize() this.client = TestCommon.CreateCosmosClient(true); this.database = await this.client.CreateDatabaseIfNotExistsAsync("mydb"); - this.containerProperties = new ContainerProperties("mycoll", new List { "/ZipCode", "/Address" }); + this.containerProperties = new ContainerProperties("mycoll", new List { "/ZipCode", "/City" }); this.container = await this.database.CreateContainerAsync(this.containerProperties); } @@ -37,6 +36,8 @@ public async Task TestInitialize() public async Task Cleanup() { await this.database.DeleteAsync(); + this.client.Dispose(); + HttpConstants.Versions.CurrentVersion = this.currentVersion; this.client.Dispose(); } @@ -44,25 +45,38 @@ public async Task Cleanup() [TestMethod] public async Task MultiHashCreateDocumentTest() { + Cosmos.PartitionKey pKey; //Document create test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + pKey= new PartitionKeyBuilder() + .Add(doc.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("City")) + .Build(); + documents[0] = await this.container.CreateItemAsync(doc, pKey); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + pKey = new PartitionKeyBuilder() + .Add(doc.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("City")) + .Build(); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + pKey = new PartitionKeyBuilder() + .Add(doc.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("City")) + .Build(); + documents[2] = await this.container.CreateItemAsync(doc); Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); @@ -72,14 +86,16 @@ public async Task MultiHashCreateDocumentTest() foreach (Document document in documents) { badPKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("ZipCode")) .Build(); document.Id += "Bad"; - ArgumentException createException = await Assert.ThrowsExceptionAsync(() => + CosmosException createException = await Assert.ThrowsExceptionAsync(() => this.container.CreateItemAsync(document, badPKey) ); + + Assert.AreEqual(createException.StatusCode, HttpStatusCode.BadRequest); } } @@ -91,45 +107,44 @@ public async Task MultiHashDeleteDocumentTest() //Create Items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Document Delete Test foreach (Document document in documents) { - //Negative test - using incomplete partition key + //Negative test - using incomplete partition key (try one with more values too) badPKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("ZipCode")) .Build(); CosmosException deleteException = await Assert.ThrowsExceptionAsync(() => this.container.DeleteItemAsync(document.Id, badPKey) ); - Assert.AreEqual(deleteException.StatusCode, HttpStatusCode.BadRequest); //Positive test pKey = new PartitionKeyBuilder() .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("City")) .Build(); - Document readDocument = (await this.container.DeleteItemAsync(document.Id, pKey)).Resource; + Document deleteDocument = (await this.container.DeleteItemAsync(document.Id, pKey)).Resource; CosmosException clientException = await Assert.ThrowsExceptionAsync(() => this.container.ReadItemAsync(document.Id, pKey) @@ -147,30 +162,30 @@ public async Task MultiHashReadItemTest() //Create Items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Document Read Test foreach (Document document in documents) { pKey = new PartitionKeyBuilder() .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("City")) .Build(); Document readDocument = (await this.container.ReadItemAsync(document.Id, pKey)).Resource; @@ -178,7 +193,7 @@ public async Task MultiHashReadItemTest() //Negative test - using incomplete partition key badPKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("ZipCode")) .Build(); CosmosException clientException = await Assert.ThrowsExceptionAsync(() => @@ -193,40 +208,47 @@ public async Task MultiHashReadItemTest() public async Task MultiHashReadManyTest() { Cosmos.PartitionKey pKey; + Cosmos.PartitionKey badPKey; //Create Items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Read Many Test List<(string, Cosmos.PartitionKey)> itemList = new List<(string, Cosmos.PartitionKey)>(); + List<(string, Cosmos.PartitionKey)> incompleteList = new List<(string, Cosmos.PartitionKey)>(); foreach (Document document in documents) { pKey = new PartitionKeyBuilder() .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("City")) + .Build(); + + badPKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) .Build(); itemList.Add((document.Id, pKey)); + incompleteList.Add((document.Id, badPKey)); } - FeedResponse feedResponse = await this.container.ReadManyItemsAsync(itemList); + FeedResponse feedResponse = await this.container.ReadManyItemsAsync(itemList); Assert.IsNotNull(feedResponse); Assert.AreEqual(feedResponse.Count, 3); @@ -234,17 +256,25 @@ public async Task MultiHashReadManyTest() Assert.IsNotNull(feedResponse.Diagnostics); int count = 0; - foreach (ToDoActivity item in feedResponse) + foreach (Document item in feedResponse) { count++; Assert.IsNotNull(item); - Assert.IsNotNull(item.pk); } Assert.AreEqual(count, 3); + + //Negative test - using incomplete partition key + await Assert.ThrowsExceptionAsync(() => + this.container.ReadManyItemsAsync(incompleteList)); } + public record DatabaseItem( + string Id, + string Pk + ); + [TestMethod] - public async Task MultiHashUpsetItemTest() + public async Task MultiHashUpsertItemTest() { Cosmos.PartitionKey pKey; Cosmos.PartitionKey badPKey; @@ -252,90 +282,92 @@ public async Task MultiHashUpsetItemTest() //Create Items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Document Upsert Test - doc1 = new Document { Id = "document4" }; - doc1.SetValue("ZipCode", "97756"); - doc1.SetValue("Address", "Redmond"); - doc1.SetValue("Type", "Residence"); + doc = new Document { Id = "document4" }; + doc.SetValue("ZipCode", "97756"); + doc.SetValue("City", "Redmond"); + doc.SetValue("Type", "Residence"); pKey = new PartitionKeyBuilder() - .Add(doc1.GetPropertyValue("ZipCode")) - .Add(doc1.GetPropertyValue("Address")) + .Add(doc.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("City")) .Build(); //insert check - await this.container.UpsertItemAsync(doc1, pKey); + await this.container.UpsertItemAsync(doc, pKey); - Document readCheck = (await this.container.ReadItemAsync(doc1.Id, pKey)).Resource; + Document readCheck = (await this.container.ReadItemAsync(doc.Id, pKey)).Resource; - Assert.AreEqual(doc1.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); - Assert.AreEqual(doc1.GetPropertyValue("Address"), readCheck.GetPropertyValue("Address")); - Assert.AreEqual(doc1.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); + Assert.AreEqual(doc.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); + Assert.AreEqual(doc.GetPropertyValue("City"), readCheck.GetPropertyValue("City")); + Assert.AreEqual(doc.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); - doc1 = new Document { Id = "document4" }; - doc1.SetValue("ZipCode", "97756"); - doc1.SetValue("Address", "Redmond"); - doc1.SetValue("Type", "Business"); + doc = new Document { Id = "document4" }; + doc.SetValue("ZipCode", "97756"); + doc.SetValue("City", "Redmond"); + doc.SetValue("Type", "Business"); //update check pKey = new PartitionKeyBuilder() - .Add(doc1.GetPropertyValue("ZipCode")) - .Add(doc1.GetPropertyValue("Address")) + .Add(doc.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("City")) .Build(); - documents.Append>(await this.container.UpsertItemAsync(doc1, pKey)); + documents.Append>(await this.container.UpsertItemAsync(doc, pKey)); - readCheck = (await this.container.ReadItemAsync(doc1.Id, pKey)).Resource; + readCheck = (await this.container.ReadItemAsync(doc.Id, pKey)).Resource; - Assert.AreEqual(doc1.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); - Assert.AreEqual(doc1.GetPropertyValue("Address"), readCheck.GetPropertyValue("Address")); - Assert.AreEqual(doc1.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); + Assert.AreEqual(doc.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); + Assert.AreEqual(doc.GetPropertyValue("City"), readCheck.GetPropertyValue("City")); + Assert.AreEqual(doc.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); count = 0; - foreach (Document doc in this.container.GetItemLinqQueryable(true)) + foreach (Document document in this.container.GetItemLinqQueryable(true)) { count++; } Assert.AreEqual(4, count); //Negative test - using incomplete partition key - doc1 = new Document { Id = "document4" }; - doc1.SetValue("ZipCode", "97756"); - doc1.SetValue("Address", "Redmond"); - doc1.SetValue("Type", "Residence"); + doc = new Document { Id = "document4" }; + doc.SetValue("ZipCode", "97756"); + doc.SetValue("City", "Redmond"); + doc.SetValue("Type", "Residence"); badPKey = new PartitionKeyBuilder() - .Add(doc1.GetPropertyValue("ZipCode")) + .Add(doc.GetPropertyValue("ZipCode")) .Build(); - await Assert.ThrowsExceptionAsync(() => - this.container.UpsertItemAsync(doc1, badPKey) + CosmosException clientException = await Assert.ThrowsExceptionAsync(() => + this.container.UpsertItemAsync(doc, badPKey) ); - readCheck = (await this.container.ReadItemAsync(doc1.Id, pKey)).Resource; + Assert.AreEqual(clientException.StatusCode, HttpStatusCode.BadRequest); + + readCheck = (await this.container.ReadItemAsync(doc.Id, pKey)).Resource; - Assert.AreEqual(doc1.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); - Assert.AreEqual(doc1.GetPropertyValue("Address"), readCheck.GetPropertyValue("Address")); - Assert.AreNotEqual(doc1.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); + Assert.AreEqual(doc.GetPropertyValue("ZipCode"), readCheck.GetPropertyValue("ZipCode")); + Assert.AreEqual(doc.GetPropertyValue("City"), readCheck.GetPropertyValue("City")); + Assert.AreNotEqual(doc.GetPropertyValue("Type"), readCheck.GetPropertyValue("Type")); } [TestMethod] @@ -346,30 +378,30 @@ public async Task MultiHashReplaceItemTest() //Create items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Document Replace Test foreach (Document document in documents) { pKey = new PartitionKeyBuilder() .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("City")) .Build(); @@ -383,14 +415,16 @@ public async Task MultiHashReplaceItemTest() //Negative test - using incomplete partition key badPKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("ZipCode")) .Build(); readDocument.SetValue("Type", "Goverment"); - await Assert.ThrowsExceptionAsync(() => + CosmosException clientException = await Assert.ThrowsExceptionAsync(() => this.container.ReplaceItemAsync(document, document.Id, partitionKey: badPKey) ); + + Assert.AreEqual(clientException.StatusCode, HttpStatusCode.BadRequest); } } @@ -398,37 +432,59 @@ await Assert.ThrowsExceptionAsync(() => public async Task MultiHashQueryItemTest() { Cosmos.PartitionKey pKey; + Cosmos.PartitionKey badPKey; //Create items for test ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - doc1.SetValue("Type", "Residence"); - documents[0] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - doc1.SetValue("Type", "Business"); - documents[1] = await this.container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - doc1.SetValue("Type", "Goverment"); - documents[2] = await this.container.CreateItemAsync(doc1); + Document doc = new Document { Id = "document1" }; + doc.SetValue("ZipCode", "500026"); + doc.SetValue("City", "Secunderabad"); + doc.SetValue("Type", "Residence"); + documents[0] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document2" }; + doc.SetValue("ZipCode", "15232"); + doc.SetValue("City", "Pittsburgh"); + doc.SetValue("Type", "Business"); + documents[1] = await this.container.CreateItemAsync(doc); + + doc = new Document { Id = "document3" }; + doc.SetValue("ZipCode", "11790"); + doc.SetValue("City", "Stonybrook"); + doc.SetValue("Type", "Goverment"); + documents[2] = await this.container.CreateItemAsync(doc); //Query foreach (Document document in documents) { pKey = new PartitionKeyBuilder() .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) + .Add(document.GetPropertyValue("City")) .Build(); - String query = $"SELECT * from c where c.id = {document.GetPropertyValue("Id")}"; + badPKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("City")) + .Build(); + + String query = $"SELECT * from c where c.id = \"{document.GetPropertyValue("id")}\""; + + using (FeedIterator feedIterator = this.container.GetItemQueryIterator( + query, + null, + new QueryRequestOptions() { PartitionKey = pKey })) + { + Assert.IsTrue(feedIterator.HasMoreResults); + + FeedResponse queryDoc = await feedIterator.ReadNextAsync(); + queryDoc.First(); + Assert.IsTrue(queryDoc.Count == 1); + feedIterator.Dispose(); + } + //Using an incomplete partition key with prefix of PK path definition + pKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) + .Build(); using (FeedIterator feedIterator = this.container.GetItemQueryIterator( query, null, @@ -437,11 +493,25 @@ public async Task MultiHashQueryItemTest() Assert.IsTrue(feedIterator.HasMoreResults); FeedResponse queryDoc = await feedIterator.ReadNextAsync(); + queryDoc.First(); + Assert.IsTrue(queryDoc.Count == 1); + feedIterator.Dispose(); } + //Negative test - using incomplete partition key + using (FeedIterator badFeedIterator = this.container.GetItemQueryIterator( + query, + null, + new QueryRequestOptions() { PartitionKey = badPKey})) + { + FeedResponse queryDocBad = await badFeedIterator.ReadNextAsync(); + Assert.ThrowsException(() => + queryDocBad.First() + ); + badFeedIterator.Dispose(); + } } } } -} -#endif +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedRangeCreateFromPartitionKeyAsyncEmulatorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedRangeCreateFromPartitionKeyAsyncEmulatorTests.cs new file mode 100644 index 0000000000..6df5b0a66f --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedRangeCreateFromPartitionKeyAsyncEmulatorTests.cs @@ -0,0 +1,344 @@ +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using System.Xml; + using Microsoft.Azure.Cosmos; + using Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Database = Database; + using PartitionKey = PartitionKey; + using PartitionKeyDefinitionVersion = PartitionKeyDefinitionVersion; + + /// + /// Testing Prefix and Full Partition for , against a with Hierarchical Partition Keys. + /// + /// + [TestClass] + public class FeedRangeCreateFromPartitionKeyAsyncEmulatorTests + { + private CosmosClient client = null; + private Database database = null; + + private readonly string currentVersion = HttpConstants.Versions.CurrentVersion; + + [TestInitialize] + public async Task TestInit() + { + HttpConstants.Versions.CurrentVersion = "2020-07-15"; + this.client = TestCommon.CreateCosmosClient(true); + + string databaseName = Guid.NewGuid().ToString(); + DatabaseResponse databaseResponse = await this.client.CreateDatabaseIfNotExistsAsync(databaseName); + this.database = databaseResponse; + } + + [TestCleanup] + public async Task TestCleanup() + { + await this.database.DeleteAsync(); + this.client.Dispose(); + + HttpConstants.Versions.CurrentVersion = this.currentVersion; + } + + /// + /// Using to create a new with Hierarchical Partition Keys. + /// Using with a Prefix partition on a MultiHash V2 . + /// + /// + /// + [TestMethod] + public async Task GetChangeFeedIteratorWithPrefixPartitionKeyReturnsFeedIterator() + { + Container container = await this.database.CreateContainerIfNotExistsAsync(new(id: @"TestMultiHashedContainer", partitionKeyPaths: new List() { "/city", "/state", "/zipCode" })); + ContainerProperties containerProperties = await container.ReadContainerAsync(); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98502" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Build(); + + FeedRange feedRange = new FeedRangePartitionKey(partitionKey); + FeedIterator iterator = container.GetChangeFeedIterator(ChangeFeedStartFrom.Beginning(feedRange), ChangeFeedMode.Incremental); + FeedResponse response = await iterator.ReadNextAsync(); + + string json = JsonConvert.SerializeObject(response.First()); + JObject @object = JObject.Parse(json); + + Assert.AreEqual(expected: item.id, actual: @object["id"]); + Assert.AreEqual(expected: item.city, actual: @object["city"]); + Assert.AreEqual(expected: item.state, actual: @object["state"]); + Assert.AreEqual(expected: item.zipCode, actual: @object["zipCode"]); + } + + /// + /// Using to create a new with hierarchical partition keys. + /// Using with a Prefix Partition on a MultiHash . + /// + /// + /// + [TestMethod] + public async Task GetChangeFeedStreamIteratorWithPrefixPartitionKeyReturnsFeedIterator() + { + Container container = await this.database.CreateContainerIfNotExistsAsync(new(id: @"TestMultiHashedContainer", partitionKeyPaths: new List() { "/city", "/state", "/zipCode" })); + ContainerProperties containerProperties = await container.ReadContainerAsync(); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98502" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Build(); + + FeedRange feedRange = new FeedRangePartitionKey(partitionKey); + using (FeedIterator iterator = container.GetChangeFeedStreamIterator(ChangeFeedStartFrom.Beginning(feedRange), ChangeFeedMode.Incremental)) + { + ResponseMessage responseMessage = await iterator.ReadNextAsync(); + + using (StreamReader streamReader = new(responseMessage.Content)) + { + string content = await streamReader.ReadToEndAsync(); + + JObject @object = JObject.Parse(content); + JToken token = @object["Documents"].First(); + + Assert.AreEqual(expected: item.id, actual: token["id"]); + Assert.AreEqual(expected: item.city, actual: token["city"]); + Assert.AreEqual(expected: item.state, actual: token["state"]); + Assert.AreEqual(expected: item.zipCode, actual: token["zipCode"]); + } + } + } + + /// + /// Using to create a new with Hierarchical Partition Keys. + /// using with a Prefix partition on a MultiHash V2 . + /// + /// + /// + [TestMethod] + [Ignore("Query is returning 'Partition key provided either doesn't correspond to definition in the collection or doesn't match partition key field values specified in the document.' Investigation.")] + [Owner("naga.naravamakula")] + public async Task GetItemQueryIteratorWithPrefixPartitionKeyReturnsFeedIterator() + { + Container container = await this.database.CreateContainerIfNotExistsAsync(new(id: @"TestMultiHashedContainer", partitionKeyPaths: new List() { "/city", "/state", "/zipCode" })); + ContainerProperties containerProperties = await container.ReadContainerAsync(); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98052" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + QueryDefinition queryDefinition = new QueryDefinition(query: "SELECT * FROM c WHERE c.city = @cityInput AND c.state = @stateInput") + .WithParameter("@cityInput", "Redmond") + .WithParameter("@stateInput", "WA"); + + partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Build(); + + FeedRange feedRange = new FeedRangePartitionKey(partitionKey); + Console.WriteLine(feedRange.ToJsonString()); + using (FeedIterator iterator = container.GetItemQueryIterator(feedRange: feedRange, queryDefinition: queryDefinition, requestOptions: new() { PartitionKey = partitionKey })) + { + FeedResponse feedResponse = await iterator.ReadNextAsync(); + + string content = JsonConvert.SerializeObject(feedResponse.First()); + JObject @object = JObject.Parse(content); + + Assert.AreEqual(expected: item.id, actual: @object["id"]); + Assert.AreEqual(expected: item.city, actual: @object["city"]); + Assert.AreEqual(expected: item.state, actual: @object["state"]); + Assert.AreEqual(expected: item.zipCode, actual: @object["zipCode"]); + } + } + + /// + /// Using to create a new with Hierarchical Partition Keys. + /// Using with a Prefix Partition on a MultiHash V2 . + /// + /// + /// + [TestMethod] + public async Task GetItemQueryStreamIteratorWithPrefixPartitionKeyReturnsFeedIterator() + { + Container container = await this.database.CreateContainerIfNotExistsAsync(new(id: @"TestMultiHashedContainer", partitionKeyPaths: new List() { "/city", "/state", "/zipCode" })); + ContainerProperties containerProperties = await container.ReadContainerAsync(); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98052" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + QueryDefinition queryDefinition = new QueryDefinition(query: "SELECT * FROM c WHERE c.city = @cityInput AND c.state = @stateInput") + .WithParameter("@cityInput", "Redmond") + .WithParameter("@stateInput", "WA"); + + using (FeedIterator iterator = container.GetItemQueryStreamIterator(queryDefinition: queryDefinition, requestOptions: new() { PartitionKey = partitionKey })) + { + ResponseMessage responseMessage = await iterator.ReadNextAsync(); + + using (StreamReader streamReader = new(responseMessage.Content)) + { + string content = await streamReader.ReadToEndAsync(); + + JObject @object = JObject.Parse(content); + JToken token = @object["Documents"].First(); + + Assert.AreEqual(expected: item.id, actual: token["id"]); + Assert.AreEqual(expected: item.city, actual: token["city"]); + Assert.AreEqual(expected: item.state, actual: token["state"]); + Assert.AreEqual(expected: item.zipCode, actual: token["zipCode"]); + } + } + } + + /// + /// Using to create a new with Hierarchical Partition Keys. + /// Using with a Full Partition on a MultiHash V2 . + /// + /// + /// + [TestMethod] + public async Task ReadItemWithFullPartitionKeyReturnsFeedIterator() + { + ContainerProperties containerProperties = new(id: @"TestMultiHashedContainer", partitionKeyDefinition: new Documents.PartitionKeyDefinition + { + Kind = Documents.PartitionKind.MultiHash, + Version = Documents.PartitionKeyDefinitionVersion.V2, + Paths = new System.Collections.ObjectModel.Collection(new List() { "/city", "/state", "/zipCode" }) + }); + + Container container = await this.database.CreateContainerIfNotExistsAsync(containerProperties: containerProperties); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98052" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + ItemResponse itemResponse = await container.ReadItemAsync(id: item.id, partitionKey: partitionKey); + + string content = JsonConvert.SerializeObject(itemResponse.Resource); + JObject @object = JObject.Parse(content); + + Assert.AreEqual(expected: item.id, actual: @object["id"]); + Assert.AreEqual(expected: item.city, actual: @object["city"]); + Assert.AreEqual(expected: item.state, actual: @object["state"]); + Assert.AreEqual(expected: item.zipCode, actual: @object["zipCode"]); + } + + /// + /// Using to create a new with Hierarchical Partition Keys. + /// Using + /// Using with a Full Partition on a MultiHash V2 . + /// + /// + /// + [TestMethod] + public async Task ReadItemStreamWithFullPartitionKeyReturnsFeedIterator() + { + Container container = await this.database.CreateContainerIfNotExistsAsync(new(id: @"TestMultiHashedContainer", partitionKeyPaths: new List() { "/city", "/state", "/zipCode" })); + ContainerProperties containerProperties = await container.ReadContainerAsync(); + + Assert.AreEqual(expected: PartitionKeyDefinitionVersion.V2, actual: containerProperties.PartitionKeyDefinitionVersion); + Assert.AreEqual(expected: 3, actual: containerProperties.PartitionKey.Paths.Count); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/city")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/state")); + Assert.IsTrue(containerProperties.PartitionKey.Paths.Contains("/zipCode")); + Assert.AreEqual(expected: Documents.PartitionKind.MultiHash, actual: containerProperties.PartitionKey.Kind); + + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98052" }; + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Add(item.zipCode) + .Build(); + + _ = await container.CreateItemAsync(item: item, partitionKey: partitionKey); + + using (ResponseMessage responseMessage = await container.ReadItemStreamAsync(id: item.id, partitionKey: partitionKey)) + { + using (StreamReader streamReader = new(responseMessage.Content)) + { + string content = await streamReader.ReadToEndAsync(); + JObject @object = JObject.Parse(content); + + Assert.AreEqual(expected: item.id, actual: @object["id"]); + Assert.AreEqual(expected: item.city, actual: @object["city"]); + Assert.AreEqual(expected: item.state, actual: @object["state"]); + Assert.AreEqual(expected: item.zipCode, actual: @object["zipCode"]); + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs index 8884e22815..ae5dccd2c8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System; using System.Collections; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; @@ -438,6 +439,153 @@ public void TestAggregateExceptionConverter() Assert.IsTrue(response.ErrorMessage.Contains(errorMessage)); } + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangePartitionKeyAndMultiHashContainerAsync() + { + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond", state = "WA", zipCode = "98502" }; + Cosmos.PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Add(item.state) + .Build(); + + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.MultiHash, + Paths = new Collection(new List() { "/city", "/state", "/zipCode" }) + }, + inputFeedRange: new FeedRangePartitionKey(partitionKey), + expectedFeedRange: new FeedRangeEpk(new Documents.Routing.Range( + min: "01620B162169497AFD85FA66E99F73760845FB119899DE50766A2C4CEFC2FA73", + max: "01620B162169497AFD85FA66E99F73760845FB119899DE50766A2C4CEFC2FA73FF", + isMinInclusive: true, + isMaxInclusive: default)), + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Once()); + } + + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangePartitionKeyOnHashContainerAsync() + { + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond" }; + Cosmos.PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Build(); + + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.Hash, + Paths = new Collection(new List() { "/city" }) + }, + inputFeedRange: new FeedRangePartitionKey(partitionKey), + expectedFeedRange: new FeedRangePartitionKey(partitionKey), + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Once()); + } + + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangePartitionKeyOnRangeContainerAsync() + { + dynamic item = new { id = Guid.NewGuid().ToString(), city = "Redmond" }; + Cosmos.PartitionKey partitionKey = new PartitionKeyBuilder() + .Add(item.city) + .Build(); + + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.Range, + Paths = new Collection(new List() { "/city" }) + }, + inputFeedRange: new FeedRangePartitionKey(partitionKey), + expectedFeedRange: new FeedRangePartitionKey(partitionKey), + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Once()); + } + + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangeEpkOnMultiHashContainerAsync() + { + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.MultiHash, + Paths = new Collection(new List() { "/city", "/state", "/zipCode" }) + }, + inputFeedRange: FeedRangeEpk.FullRange, + expectedFeedRange: FeedRangeEpk.FullRange, + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Never()); + } + + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangeEpkOnHashContainerAsync() + { + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.Hash, + Paths = new Collection(new List() { "/city" }) + }, + inputFeedRange: FeedRangeEpk.FullRange, + expectedFeedRange: FeedRangeEpk.FullRange, + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Never()); + } + + [TestMethod] + public async Task TestResolveFeedRangeBasedOnPrefixWithFeedRangeEpkOnRangeContainerAsync() + { + await HandlerTests.TestResolveFeedRangeBasedOnPrefixAsync( + partitionKeyDefinition: new PartitionKeyDefinition() + { + Kind = PartitionKind.Range, + Paths = new Collection(new List() { "/city" }) + }, + inputFeedRange: FeedRangeEpk.FullRange, + expectedFeedRange: FeedRangeEpk.FullRange, + getPartitionKeyDefinitionAsyncExecutions: Moq.Times.Never()); + } + + private static async Task TestResolveFeedRangeBasedOnPrefixAsync( + PartitionKeyDefinition partitionKeyDefinition, + FeedRangeInternal inputFeedRange, + FeedRangeInternal expectedFeedRange, + Moq.Times getPartitionKeyDefinitionAsyncExecutions) + where TFeedRange : FeedRangeInternal + { + using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient( + accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong, + customizeClientBuilder: builder => builder.WithConsistencyLevel(Cosmos.ConsistencyLevel.Eventual)); + + Moq.Mock mockContainer = MockCosmosUtil.CreateMockContainer( + dbName: Guid.NewGuid().ToString(), + containerName: Guid.NewGuid().ToString()); + + CancellationToken cancellationToken = CancellationToken.None; + + mockContainer + .Setup(container => container.GetPartitionKeyDefinitionAsync(cancellationToken)) + .Returns(Task.FromResult(partitionKeyDefinition)); + + RequestInvokerHandler invoker = new( + client: client, + requestedClientConsistencyLevel: default); + + Cosmos.FeedRange feedRange = await RequestInvokerHandler.ResolveFeedRangeBasedOnPrefixContainerAsync( + feedRange: inputFeedRange, + cosmosContainerCore: mockContainer.Object, + cancellationToken: cancellationToken); + + mockContainer.Verify(x => x.GetPartitionKeyDefinitionAsync(Moq.It.IsAny()), getPartitionKeyDefinitionAsyncExecutions); + + Assert.IsNotNull(feedRange, "FeedRange did not initialize"); + + Assert.IsInstanceOfType( + value: feedRange, + expectedType: typeof(TFeedRange)); + + Assert.AreEqual( + expected: expectedFeedRange.ToJsonString(), + actual: feedRange.ToJsonString()); + } + private class SomePayload { public string V1 { get; set; }